@mooncompany/uplink-chat 0.5.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.
Potentially problematic release.
This version of @mooncompany/uplink-chat might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/bin/uplink.js +279 -0
- package/middleware/error-handler.js +69 -0
- package/package.json +93 -0
- package/public/css/agents.36b98c0f.css +1469 -0
- package/public/css/agents.css +1469 -0
- package/public/css/app.a6a7f8f5.css +2731 -0
- package/public/css/app.css +2731 -0
- package/public/css/artifacts.css +444 -0
- package/public/css/commands.css +55 -0
- package/public/css/connection.css +131 -0
- package/public/css/dashboard.css +233 -0
- package/public/css/developer.css +328 -0
- package/public/css/files.css +123 -0
- package/public/css/markdown.css +156 -0
- package/public/css/message-actions.css +278 -0
- package/public/css/mobile.css +614 -0
- package/public/css/panels-unified.css +483 -0
- package/public/css/premium.css +415 -0
- package/public/css/realtime.css +189 -0
- package/public/css/satellites.css +401 -0
- package/public/css/shortcuts.css +185 -0
- package/public/css/split-view.4def0262.css +673 -0
- package/public/css/split-view.css +673 -0
- package/public/css/theme-generator.css +391 -0
- package/public/css/themes.css +387 -0
- package/public/css/timestamps.css +54 -0
- package/public/css/variables.css +78 -0
- package/public/dist/bundle.b55050c4.js +15757 -0
- package/public/favicon.svg +24 -0
- package/public/img/agents/ada.png +0 -0
- package/public/img/agents/clarice.png +0 -0
- package/public/img/agents/dennis-nedry.png +0 -0
- package/public/img/agents/elliot-alderson.png +0 -0
- package/public/img/agents/main.png +0 -0
- package/public/img/agents/scotty.png +0 -0
- package/public/img/agents/top-flight-security.png +0 -0
- package/public/index.html +1083 -0
- package/public/js/agents-data.js +234 -0
- package/public/js/agents-ui.js +72 -0
- package/public/js/agents.js +1525 -0
- package/public/js/app.js +79 -0
- package/public/js/appearance-settings.js +111 -0
- package/public/js/artifacts.js +432 -0
- package/public/js/audio-queue.js +168 -0
- package/public/js/bootstrap.js +54 -0
- package/public/js/chat.js +1211 -0
- package/public/js/commands.js +581 -0
- package/public/js/connection-api.js +121 -0
- package/public/js/connection.js +1231 -0
- package/public/js/context-tracker.js +271 -0
- package/public/js/core.js +172 -0
- package/public/js/dashboard.js +452 -0
- package/public/js/developer.js +432 -0
- package/public/js/encryption.js +124 -0
- package/public/js/errors.js +122 -0
- package/public/js/event-bus.js +77 -0
- package/public/js/fetch-utils.js +171 -0
- package/public/js/file-handler.js +229 -0
- package/public/js/files.js +352 -0
- package/public/js/gateway-chat.js +538 -0
- package/public/js/logger.js +112 -0
- package/public/js/markdown.js +190 -0
- package/public/js/message-actions.js +431 -0
- package/public/js/message-renderer.js +288 -0
- package/public/js/missed-messages.js +235 -0
- package/public/js/mobile-debug.js +95 -0
- package/public/js/notifications.js +367 -0
- package/public/js/offline-queue.js +178 -0
- package/public/js/onboarding.js +543 -0
- package/public/js/panels.js +156 -0
- package/public/js/premium.js +412 -0
- package/public/js/realtime-voice.js +844 -0
- package/public/js/satellite-sync.js +256 -0
- package/public/js/satellite-ui.js +175 -0
- package/public/js/satellites.js +1516 -0
- package/public/js/settings.js +1087 -0
- package/public/js/shortcuts.js +381 -0
- package/public/js/split-chat.js +1234 -0
- package/public/js/split-resize.js +211 -0
- package/public/js/splitview.js +340 -0
- package/public/js/storage.js +408 -0
- package/public/js/streaming-handler.js +324 -0
- package/public/js/stt-settings.js +316 -0
- package/public/js/theme-generator.js +661 -0
- package/public/js/themes.js +164 -0
- package/public/js/timestamps.js +198 -0
- package/public/js/tts-settings.js +575 -0
- package/public/js/ui.js +267 -0
- package/public/js/update-notifier.js +143 -0
- package/public/js/utils/constants.js +165 -0
- package/public/js/utils/sanitize.js +93 -0
- package/public/js/utils/sse-parser.js +195 -0
- package/public/js/voice.js +883 -0
- package/public/manifest.json +58 -0
- package/public/moon_texture.jpg +0 -0
- package/public/sw.js +221 -0
- package/public/three.min.js +6 -0
- package/server/channel.js +529 -0
- package/server/chat.js +270 -0
- package/server/config-store.js +362 -0
- package/server/config.js +159 -0
- package/server/context.js +131 -0
- package/server/gateway-commands.js +211 -0
- package/server/gateway-proxy.js +318 -0
- package/server/index.js +22 -0
- package/server/logger.js +89 -0
- package/server/middleware/auth.js +188 -0
- package/server/middleware.js +218 -0
- package/server/openclaw-discover.js +308 -0
- package/server/premium/index.js +156 -0
- package/server/premium/license.js +140 -0
- package/server/realtime/bridge.js +837 -0
- package/server/realtime/index.js +349 -0
- package/server/realtime/tts-stream.js +446 -0
- package/server/routes/agents.js +564 -0
- package/server/routes/artifacts.js +174 -0
- package/server/routes/chat.js +311 -0
- package/server/routes/config-settings.js +345 -0
- package/server/routes/config.js +603 -0
- package/server/routes/files.js +307 -0
- package/server/routes/index.js +18 -0
- package/server/routes/media.js +451 -0
- package/server/routes/missed-messages.js +107 -0
- package/server/routes/premium.js +75 -0
- package/server/routes/push.js +156 -0
- package/server/routes/satellite.js +406 -0
- package/server/routes/status.js +251 -0
- package/server/routes/stt.js +35 -0
- package/server/routes/voice.js +260 -0
- package/server/routes/webhooks.js +203 -0
- package/server/routes.js +206 -0
- package/server/runtime-config.js +336 -0
- package/server/share.js +305 -0
- package/server/stt/faster-whisper.js +72 -0
- package/server/stt/groq.js +51 -0
- package/server/stt/index.js +196 -0
- package/server/stt/openai.js +49 -0
- package/server/sync.js +244 -0
- package/server/tailscale-https.js +175 -0
- package/server/tts.js +646 -0
- package/server/update-checker.js +172 -0
- package/server/utils/filename.js +129 -0
- package/server/utils.js +147 -0
- package/server/watchdog.js +318 -0
- package/server/websocket/broadcast.js +359 -0
- package/server/websocket/connections.js +339 -0
- package/server/websocket/index.js +215 -0
- package/server/websocket/routing.js +277 -0
- package/server/websocket/sync.js +102 -0
- package/server.js +404 -0
- package/utils/detect-tool-usage.js +93 -0
- package/utils/errors.js +158 -0
- package/utils/html-escape.js +84 -0
- package/utils/id-sanitize.js +94 -0
- package/utils/response.js +130 -0
- package/utils/with-retry.js +105 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// KEYBOARD SHORTCUTS MODULE
|
|
3
|
+
// Keyboard shortcuts panel and customization
|
|
4
|
+
// ============================================
|
|
5
|
+
|
|
6
|
+
const STORAGE_KEY = 'uplink-shortcuts';
|
|
7
|
+
|
|
8
|
+
// Default shortcuts
|
|
9
|
+
const defaultShortcuts = {
|
|
10
|
+
send: { key: 'Enter', description: 'Send message' },
|
|
11
|
+
newLine: { key: 'Shift+Enter', description: 'New line in message' },
|
|
12
|
+
clearChat: { key: 'Ctrl+Shift+Delete', description: 'Clear chat' },
|
|
13
|
+
toggleVoice: { key: 'Ctrl+Shift+V', description: 'Toggle voice mode' },
|
|
14
|
+
focusInput: { key: 'Ctrl+/', description: 'Focus message input' },
|
|
15
|
+
openSettings: { key: 'Ctrl+,', description: 'Open settings' },
|
|
16
|
+
closePanel: { key: 'Escape', description: 'Close panel/cancel' },
|
|
17
|
+
showShortcuts: { key: 'Ctrl+Shift+/', description: 'Show shortcuts' }
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Current shortcuts (may be customized)
|
|
21
|
+
let shortcuts = { ...defaultShortcuts };
|
|
22
|
+
|
|
23
|
+
// Panel element
|
|
24
|
+
let panelEl = null;
|
|
25
|
+
|
|
26
|
+
function init() {
|
|
27
|
+
// Load saved shortcuts
|
|
28
|
+
loadShortcuts();
|
|
29
|
+
|
|
30
|
+
// Create panel
|
|
31
|
+
createPanel();
|
|
32
|
+
|
|
33
|
+
// Add button to settings
|
|
34
|
+
addSettingsButton();
|
|
35
|
+
|
|
36
|
+
// Register global shortcuts
|
|
37
|
+
registerShortcuts();
|
|
38
|
+
|
|
39
|
+
console.log('Shortcuts: Initialized');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function loadShortcuts() {
|
|
43
|
+
try {
|
|
44
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
45
|
+
if (saved) {
|
|
46
|
+
const parsed = JSON.parse(saved);
|
|
47
|
+
shortcuts = { ...defaultShortcuts, ...parsed };
|
|
48
|
+
}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error('Shortcuts: Failed to load', e);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function saveShortcuts() {
|
|
55
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(shortcuts));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Currently editing shortcut
|
|
59
|
+
let editingShortcutId = null;
|
|
60
|
+
|
|
61
|
+
function createPanel() {
|
|
62
|
+
panelEl = document.createElement('div');
|
|
63
|
+
panelEl.className = 'shortcuts-panel';
|
|
64
|
+
panelEl.innerHTML = `
|
|
65
|
+
<div class="shortcuts-panel-inner">
|
|
66
|
+
<div class="shortcuts-panel-header">
|
|
67
|
+
<h3>⌨️ Keyboard Shortcuts</h3>
|
|
68
|
+
<button class="shortcuts-close" title="Close">×</button>
|
|
69
|
+
</div>
|
|
70
|
+
<p class="shortcuts-hint">Click a shortcut to edit it</p>
|
|
71
|
+
<div class="shortcuts-list">
|
|
72
|
+
${Object.entries(shortcuts).map(([id, shortcut]) => `
|
|
73
|
+
<div class="shortcut-row" data-id="${id}">
|
|
74
|
+
<span class="shortcut-desc">${shortcut.description}</span>
|
|
75
|
+
<kbd class="shortcut-key" data-id="${id}" title="Click to edit">${formatKey(shortcut.key)}</kbd>
|
|
76
|
+
</div>
|
|
77
|
+
`).join('')}
|
|
78
|
+
</div>
|
|
79
|
+
<div class="shortcuts-footer">
|
|
80
|
+
<button class="shortcuts-reset">Reset to Defaults</button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
document.body.appendChild(panelEl);
|
|
86
|
+
|
|
87
|
+
// Close button
|
|
88
|
+
panelEl.querySelector('.shortcuts-close').addEventListener('click', hidePanel);
|
|
89
|
+
|
|
90
|
+
// Reset button
|
|
91
|
+
panelEl.querySelector('.shortcuts-reset').addEventListener('click', resetShortcuts);
|
|
92
|
+
|
|
93
|
+
// Make shortcuts editable
|
|
94
|
+
panelEl.querySelectorAll('.shortcut-key').forEach(kbd => {
|
|
95
|
+
kbd.style.cursor = 'pointer';
|
|
96
|
+
kbd.addEventListener('click', (e) => {
|
|
97
|
+
e.stopPropagation();
|
|
98
|
+
startEditingShortcut(kbd.dataset.id);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Click outside to close
|
|
103
|
+
panelEl.addEventListener('click', (e) => {
|
|
104
|
+
if (e.target === panelEl) hidePanel();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Register with panel manager for mutual exclusivity
|
|
108
|
+
if (window.UplinkPanels) {
|
|
109
|
+
window.UplinkPanels.register('shortcuts', {
|
|
110
|
+
element: panelEl,
|
|
111
|
+
isOpen: () => panelEl?.classList.contains('visible'),
|
|
112
|
+
open: () => panelEl?.classList.add('visible'),
|
|
113
|
+
close: () => panelEl?.classList.remove('visible')
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function showPanel() {
|
|
119
|
+
if (window.UplinkPanels) {
|
|
120
|
+
window.UplinkPanels.open('shortcuts');
|
|
121
|
+
} else {
|
|
122
|
+
panelEl?.classList.add('visible');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function hidePanel() {
|
|
127
|
+
if (window.UplinkPanels) {
|
|
128
|
+
window.UplinkPanels.close('shortcuts');
|
|
129
|
+
} else {
|
|
130
|
+
panelEl?.classList.remove('visible');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function togglePanel() {
|
|
135
|
+
if (window.UplinkPanels) {
|
|
136
|
+
window.UplinkPanels.toggle('shortcuts');
|
|
137
|
+
} else {
|
|
138
|
+
panelEl?.classList.toggle('visible');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatKey(key) {
|
|
143
|
+
return key
|
|
144
|
+
.replace('Ctrl', '⌃')
|
|
145
|
+
.replace('Shift', '⇧')
|
|
146
|
+
.replace('Alt', '⌥')
|
|
147
|
+
.replace('Meta', '⌘')
|
|
148
|
+
.replace('Enter', '↵')
|
|
149
|
+
.replace('Escape', 'Esc')
|
|
150
|
+
.replace('Delete', 'Del')
|
|
151
|
+
.replace(/\+/g, ' ');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function startEditingShortcut(shortcutId) {
|
|
155
|
+
// Stop any previous editing
|
|
156
|
+
stopEditingShortcut();
|
|
157
|
+
|
|
158
|
+
editingShortcutId = shortcutId;
|
|
159
|
+
const kbd = panelEl.querySelector(`.shortcut-key[data-id="${shortcutId}"]`);
|
|
160
|
+
if (kbd) {
|
|
161
|
+
kbd.classList.add('editing');
|
|
162
|
+
kbd.textContent = 'Press keys...';
|
|
163
|
+
kbd.title = 'Press new shortcut keys, Escape to cancel';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Add key capture listener
|
|
167
|
+
document.addEventListener('keydown', handleShortcutKeyCapture, true);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function stopEditingShortcut() {
|
|
171
|
+
if (!editingShortcutId) return;
|
|
172
|
+
|
|
173
|
+
const kbd = panelEl.querySelector(`.shortcut-key[data-id="${editingShortcutId}"]`);
|
|
174
|
+
if (kbd) {
|
|
175
|
+
kbd.classList.remove('editing');
|
|
176
|
+
kbd.textContent = formatKey(shortcuts[editingShortcutId].key);
|
|
177
|
+
kbd.title = 'Click to edit';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
editingShortcutId = null;
|
|
181
|
+
document.removeEventListener('keydown', handleShortcutKeyCapture, true);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function handleShortcutKeyCapture(e) {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
e.stopPropagation();
|
|
187
|
+
|
|
188
|
+
// Escape cancels editing
|
|
189
|
+
if (e.key === 'Escape') {
|
|
190
|
+
stopEditingShortcut();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Build key string
|
|
195
|
+
const parts = [];
|
|
196
|
+
if (e.ctrlKey) parts.push('Ctrl');
|
|
197
|
+
if (e.shiftKey) parts.push('Shift');
|
|
198
|
+
if (e.altKey) parts.push('Alt');
|
|
199
|
+
if (e.metaKey) parts.push('Meta');
|
|
200
|
+
|
|
201
|
+
// Don't save if only modifier keys pressed
|
|
202
|
+
const key = e.key;
|
|
203
|
+
if (['Control', 'Shift', 'Alt', 'Meta'].includes(key)) return;
|
|
204
|
+
|
|
205
|
+
// Normalize key name
|
|
206
|
+
let normalizedKey = key;
|
|
207
|
+
if (key === ' ') normalizedKey = 'Space';
|
|
208
|
+
else if (key.length === 1) normalizedKey = key.toUpperCase();
|
|
209
|
+
|
|
210
|
+
parts.push(normalizedKey);
|
|
211
|
+
const newShortcut = parts.join('+');
|
|
212
|
+
|
|
213
|
+
// Save the new shortcut
|
|
214
|
+
shortcuts[editingShortcutId].key = newShortcut;
|
|
215
|
+
saveShortcuts();
|
|
216
|
+
|
|
217
|
+
// Update display and stop editing
|
|
218
|
+
const kbd = panelEl.querySelector(`.shortcut-key[data-id="${editingShortcutId}"]`);
|
|
219
|
+
if (kbd) {
|
|
220
|
+
kbd.classList.remove('editing');
|
|
221
|
+
kbd.textContent = formatKey(newShortcut);
|
|
222
|
+
kbd.title = 'Click to edit';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
editingShortcutId = null;
|
|
226
|
+
document.removeEventListener('keydown', handleShortcutKeyCapture, true);
|
|
227
|
+
|
|
228
|
+
console.log(`Shortcuts: Updated ${editingShortcutId} to ${newShortcut}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function resetShortcuts() {
|
|
232
|
+
shortcuts = { ...defaultShortcuts };
|
|
233
|
+
saveShortcuts();
|
|
234
|
+
updatePanelDisplay();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function updatePanelDisplay() {
|
|
238
|
+
if (!panelEl) return;
|
|
239
|
+
|
|
240
|
+
Object.entries(shortcuts).forEach(([id, shortcut]) => {
|
|
241
|
+
const row = panelEl.querySelector(`.shortcut-row[data-id="${id}"]`);
|
|
242
|
+
if (row) {
|
|
243
|
+
row.querySelector('.shortcut-key').textContent = formatKey(shortcut.key);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function addSettingsButton() {
|
|
249
|
+
const settingsPanel = document.getElementById('settingsPanel');
|
|
250
|
+
if (!settingsPanel) {
|
|
251
|
+
setTimeout(addSettingsButton, 100);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check if already added
|
|
256
|
+
if (document.getElementById('shortcutsRow')) return;
|
|
257
|
+
|
|
258
|
+
const row = document.createElement('div');
|
|
259
|
+
row.className = 'setting-row';
|
|
260
|
+
row.id = 'shortcutsRow';
|
|
261
|
+
row.innerHTML = `
|
|
262
|
+
<div>
|
|
263
|
+
<div class="setting-label">Keyboard shortcuts</div>
|
|
264
|
+
<div class="setting-desc">View and customize shortcuts</div>
|
|
265
|
+
</div>
|
|
266
|
+
<button class="setting-btn" id="shortcutsBtn">View</button>
|
|
267
|
+
`;
|
|
268
|
+
|
|
269
|
+
// Insert before theme row
|
|
270
|
+
const themeRow = document.getElementById('themeSelectorRow');
|
|
271
|
+
if (themeRow) {
|
|
272
|
+
themeRow.before(row);
|
|
273
|
+
} else {
|
|
274
|
+
settingsPanel.appendChild(row);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
document.getElementById('shortcutsBtn').addEventListener('click', showPanel);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function registerShortcuts() {
|
|
281
|
+
document.addEventListener('keydown', (e) => {
|
|
282
|
+
const key = getKeyCombo(e);
|
|
283
|
+
|
|
284
|
+
// Show shortcuts panel (Ctrl+Shift+/ only - removed Shift+? as it conflicts with typing ?)
|
|
285
|
+
if (matchKey(key, shortcuts.showShortcuts.key)) {
|
|
286
|
+
e.preventDefault();
|
|
287
|
+
togglePanel();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Close panel (any open panel)
|
|
292
|
+
if (matchKey(key, shortcuts.closePanel.key)) {
|
|
293
|
+
// Use panel manager to close any open panel
|
|
294
|
+
if (window.UplinkPanels?.getCurrent()) {
|
|
295
|
+
e.preventDefault();
|
|
296
|
+
window.UplinkPanels.closeAll();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Focus input
|
|
302
|
+
if (matchKey(key, shortcuts.focusInput.key)) {
|
|
303
|
+
e.preventDefault();
|
|
304
|
+
const input = document.getElementById('textInput');
|
|
305
|
+
input?.focus();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Open settings
|
|
310
|
+
if (matchKey(key, shortcuts.openSettings.key)) {
|
|
311
|
+
e.preventDefault();
|
|
312
|
+
const settingsPanel = document.getElementById('settingsPanel');
|
|
313
|
+
settingsPanel?.classList.toggle('visible');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Toggle voice mode
|
|
318
|
+
if (matchKey(key, shortcuts.toggleVoice.key)) {
|
|
319
|
+
e.preventDefault();
|
|
320
|
+
const voiceTab = document.getElementById('voiceModeTab');
|
|
321
|
+
const textTab = document.getElementById('textModeTab');
|
|
322
|
+
const isVoice = voiceTab?.classList.contains('active');
|
|
323
|
+
if (isVoice) {
|
|
324
|
+
textTab?.click();
|
|
325
|
+
} else {
|
|
326
|
+
voiceTab?.click();
|
|
327
|
+
}
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Clear chat (via settings)
|
|
332
|
+
if (matchKey(key, shortcuts.clearChat.key)) {
|
|
333
|
+
e.preventDefault();
|
|
334
|
+
const settingsClearBtn = document.getElementById('settingsClearBtn');
|
|
335
|
+
settingsClearBtn?.click();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function getKeyCombo(e) {
|
|
342
|
+
const parts = [];
|
|
343
|
+
if (e.ctrlKey || e.metaKey) parts.push('Ctrl');
|
|
344
|
+
if (e.shiftKey) parts.push('Shift');
|
|
345
|
+
if (e.altKey) parts.push('Alt');
|
|
346
|
+
|
|
347
|
+
// Get the key name
|
|
348
|
+
let keyName = e.key;
|
|
349
|
+
if (!keyName) return parts.join('+'); // Handle undefined key
|
|
350
|
+
if (keyName === ' ') keyName = 'Space';
|
|
351
|
+
if (keyName.length === 1) keyName = keyName.toUpperCase();
|
|
352
|
+
|
|
353
|
+
// Don't add modifier keys as the key itself
|
|
354
|
+
if (!['Control', 'Shift', 'Alt', 'Meta'].includes(keyName)) {
|
|
355
|
+
parts.push(keyName);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return parts.join('+');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function matchKey(pressed, shortcut) {
|
|
362
|
+
const normalize = (k) => k.toLowerCase().replace(/\s+/g, '');
|
|
363
|
+
return normalize(pressed) === normalize(shortcut);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Expose API
|
|
367
|
+
export const UplinkShortcuts = {
|
|
368
|
+
show: showPanel,
|
|
369
|
+
hide: hidePanel,
|
|
370
|
+
toggle: togglePanel,
|
|
371
|
+
get: (id) => shortcuts[id],
|
|
372
|
+
getAll: () => ({ ...shortcuts })
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
import { UplinkCore } from './core.js';
|
|
376
|
+
|
|
377
|
+
// Backward compat: assign to window
|
|
378
|
+
window.UplinkShortcuts = UplinkShortcuts;
|
|
379
|
+
|
|
380
|
+
// Register and init
|
|
381
|
+
UplinkCore.registerModule('shortcuts', init);
|