git-watchtower 1.10.5 → 1.10.6
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/package.json +1 -1
- package/src/server/web-ui/js.js +106 -100
package/package.json
CHANGED
package/src/server/web-ui/js.js
CHANGED
|
@@ -33,26 +33,32 @@ function getDashboardJs() {
|
|
|
33
33
|
'use strict';
|
|
34
34
|
|
|
35
35
|
// ── State ──────────────────────────────────────────────────────
|
|
36
|
-
let state = null;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
36
|
+
let state = null; // server-pushed state (branches, config, etc.)
|
|
37
|
+
|
|
38
|
+
// Client-side UI state — consolidated into a single object for
|
|
39
|
+
// easier debugging (inspect ui in console) and clearer separation
|
|
40
|
+
// from the server-pushed 'state' above.
|
|
41
|
+
const ui = {
|
|
42
|
+
prevBranches: null,
|
|
43
|
+
selectedIndex: 0,
|
|
44
|
+
searchMode: false,
|
|
45
|
+
searchQuery: '',
|
|
46
|
+
confirmMode: false,
|
|
47
|
+
confirmCallback: null,
|
|
48
|
+
connected: false,
|
|
49
|
+
flashTimer: null,
|
|
50
|
+
activeTabId: null,
|
|
51
|
+
logViewerMode: false,
|
|
52
|
+
logViewerTab: 'server',
|
|
53
|
+
branchActionMode: false,
|
|
54
|
+
infoMode: false,
|
|
55
|
+
cleanupMode: false,
|
|
56
|
+
updateMode: false,
|
|
57
|
+
stashMode: false,
|
|
58
|
+
pendingStashBranch: null,
|
|
59
|
+
updateNotificationShown: false,
|
|
60
|
+
remoteTabPollTimer: null,
|
|
61
|
+
};
|
|
56
62
|
|
|
57
63
|
// ── Persistent Preferences (localStorage) ─────────────────────
|
|
58
64
|
const PREFS_KEY = 'git-watchtower-prefs';
|
|
@@ -191,27 +197,27 @@ function getDashboardJs() {
|
|
|
191
197
|
evtSource = new EventSource('/api/events');
|
|
192
198
|
|
|
193
199
|
evtSource.onopen = () => {
|
|
194
|
-
connected = true;
|
|
200
|
+
ui.connected = true;
|
|
195
201
|
updateConnectionStatus();
|
|
196
202
|
};
|
|
197
203
|
|
|
198
204
|
evtSource.addEventListener('state', (e) => {
|
|
199
205
|
try {
|
|
200
206
|
const newState = JSON.parse(e.data);
|
|
201
|
-
if (!activeTabId && newState.activeProjectId) {
|
|
202
|
-
activeTabId = newState.activeProjectId;
|
|
207
|
+
if (!ui.activeTabId && newState.activeProjectId) {
|
|
208
|
+
ui.activeTabId = newState.activeProjectId;
|
|
203
209
|
}
|
|
204
210
|
// SSE always pushes the local project's state. When the user
|
|
205
211
|
// is viewing a different tab we must NOT overwrite the per-project
|
|
206
212
|
// data (branches, PRs, activity, etc.) — only update global
|
|
207
213
|
// metadata so the tab bar, connection status, and version info
|
|
208
214
|
// stay current.
|
|
209
|
-
const viewingLocalProject = !activeTabId || activeTabId === newState.activeProjectId;
|
|
215
|
+
const viewingLocalProject = !ui.activeTabId || ui.activeTabId === newState.activeProjectId;
|
|
210
216
|
if (viewingLocalProject) {
|
|
211
217
|
if (state && state.branches) {
|
|
212
218
|
diffBranchesForNotifications(state.branches, newState.branches || []);
|
|
213
219
|
}
|
|
214
|
-
prevBranches = state ? state.branches : null;
|
|
220
|
+
ui.prevBranches = state ? state.branches : null;
|
|
215
221
|
state = newState;
|
|
216
222
|
} else {
|
|
217
223
|
if (state) {
|
|
@@ -240,7 +246,7 @@ function getDashboardJs() {
|
|
|
240
246
|
try {
|
|
241
247
|
const data = JSON.parse(e.data);
|
|
242
248
|
if (!data.success && data.message && data.message.indexOf('uncommitted') !== -1) {
|
|
243
|
-
pendingStashBranch = data.branch || null;
|
|
249
|
+
ui.pendingStashBranch = data.branch || null;
|
|
244
250
|
showErrorToastWithHint(data.message, 'Press S to stash');
|
|
245
251
|
} else {
|
|
246
252
|
showToast(data.message, data.success ? 'success' : 'error');
|
|
@@ -249,7 +255,7 @@ function getDashboardJs() {
|
|
|
249
255
|
});
|
|
250
256
|
|
|
251
257
|
evtSource.onerror = () => {
|
|
252
|
-
connected = false;
|
|
258
|
+
ui.connected = false;
|
|
253
259
|
updateConnectionStatus();
|
|
254
260
|
};
|
|
255
261
|
}
|
|
@@ -257,8 +263,8 @@ function getDashboardJs() {
|
|
|
257
263
|
function updateConnectionStatus() {
|
|
258
264
|
const dot = document.getElementById('connection-dot');
|
|
259
265
|
const badge = document.getElementById('status-badge');
|
|
260
|
-
if (connected) {
|
|
261
|
-
dot.className = 'connection-dot connected';
|
|
266
|
+
if (ui.connected) {
|
|
267
|
+
dot.className = 'connection-dot ui.connected';
|
|
262
268
|
badge.className = 'badge badge-online';
|
|
263
269
|
badge.textContent = 'live';
|
|
264
270
|
} else {
|
|
@@ -274,7 +280,7 @@ function getDashboardJs() {
|
|
|
274
280
|
xhr.open('POST', '/api/action');
|
|
275
281
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
276
282
|
const data = { action, payload: payload || {} };
|
|
277
|
-
if (activeTabId) data.projectId = activeTabId;
|
|
283
|
+
if (ui.activeTabId) data.projectId = ui.activeTabId;
|
|
278
284
|
xhr.send(JSON.stringify(data));
|
|
279
285
|
}
|
|
280
286
|
|
|
@@ -283,8 +289,8 @@ function getDashboardJs() {
|
|
|
283
289
|
const el = document.getElementById('flash');
|
|
284
290
|
el.textContent = text;
|
|
285
291
|
el.className = 'flash visible ' + (type || 'info');
|
|
286
|
-
clearTimeout(flashTimer);
|
|
287
|
-
flashTimer = setTimeout(() => { el.className = 'flash'; }, 3000);
|
|
292
|
+
clearTimeout(ui.flashTimer);
|
|
293
|
+
ui.flashTimer = setTimeout(() => { el.className = 'flash'; }, 3000);
|
|
288
294
|
}
|
|
289
295
|
|
|
290
296
|
// ── Toast Notifications ────────────────────────────────────────
|
|
@@ -337,7 +343,7 @@ function getDashboardJs() {
|
|
|
337
343
|
};
|
|
338
344
|
|
|
339
345
|
function anyModalOpen() {
|
|
340
|
-
return _openModals.length > 0 || confirmMode;
|
|
346
|
+
return _openModals.length > 0 || ui.confirmMode;
|
|
341
347
|
}
|
|
342
348
|
|
|
343
349
|
// Create modal instances
|
|
@@ -349,18 +355,18 @@ function getDashboardJs() {
|
|
|
349
355
|
const updateModal = new Modal('update-overlay', 'update-close');
|
|
350
356
|
|
|
351
357
|
// Per-modal hide callbacks for state cleanup
|
|
352
|
-
logViewerModal.onHide = () => { logViewerMode = false; };
|
|
353
|
-
branchActionModal.onHide = () => { branchActionMode = false; };
|
|
354
|
-
infoModal.onHide = () => { infoMode = false; };
|
|
355
|
-
stashModal.onHide = () => { stashMode = false; pendingStashBranch = null; };
|
|
356
|
-
cleanupModal.onHide = () => { cleanupMode = false; };
|
|
357
|
-
updateModal.onHide = () => { updateMode = false; };
|
|
358
|
+
logViewerModal.onHide = () => { ui.logViewerMode = false; };
|
|
359
|
+
branchActionModal.onHide = () => { ui.branchActionMode = false; };
|
|
360
|
+
infoModal.onHide = () => { ui.infoMode = false; };
|
|
361
|
+
stashModal.onHide = () => { ui.stashMode = false; ui.pendingStashBranch = null; };
|
|
362
|
+
cleanupModal.onHide = () => { ui.cleanupMode = false; };
|
|
363
|
+
updateModal.onHide = () => { ui.updateMode = false; };
|
|
358
364
|
|
|
359
365
|
// ── Confirm Dialog ─────────────────────────────────────────────
|
|
360
366
|
function showConfirm(title, message, onConfirm, opts) {
|
|
361
367
|
opts = opts || {};
|
|
362
|
-
confirmMode = true;
|
|
363
|
-
confirmCallback = onConfirm;
|
|
368
|
+
ui.confirmMode = true;
|
|
369
|
+
ui.confirmCallback = onConfirm;
|
|
364
370
|
const box = document.getElementById('confirm-box');
|
|
365
371
|
box.innerHTML =
|
|
366
372
|
'<div class="confirm-title">' + escHtml(title) + '</div>' +
|
|
@@ -375,13 +381,13 @@ function getDashboardJs() {
|
|
|
375
381
|
document.getElementById('confirm-cancel').onclick = hideConfirm;
|
|
376
382
|
document.getElementById('confirm-ok').onclick = () => {
|
|
377
383
|
hideConfirm();
|
|
378
|
-
if (confirmCallback) confirmCallback();
|
|
384
|
+
if (ui.confirmCallback) ui.confirmCallback();
|
|
379
385
|
};
|
|
380
386
|
}
|
|
381
387
|
|
|
382
388
|
function hideConfirm() {
|
|
383
|
-
confirmMode = false;
|
|
384
|
-
confirmCallback = null;
|
|
389
|
+
ui.confirmMode = false;
|
|
390
|
+
ui.confirmCallback = null;
|
|
385
391
|
document.getElementById('confirm-overlay').className = 'confirm-overlay';
|
|
386
392
|
}
|
|
387
393
|
|
|
@@ -398,7 +404,7 @@ function getDashboardJs() {
|
|
|
398
404
|
let html = '';
|
|
399
405
|
for (let i = 0; i < projects.length; i++) {
|
|
400
406
|
const p = projects[i];
|
|
401
|
-
const isActive = p.id === activeTabId;
|
|
407
|
+
const isActive = p.id === ui.activeTabId;
|
|
402
408
|
html += '<div class="tab' + (isActive ? ' active' : '') + '" data-project-id="' + escHtml(p.id) + '">';
|
|
403
409
|
html += '<span class="tab-dot"></span>';
|
|
404
410
|
html += escHtml(p.name);
|
|
@@ -412,7 +418,7 @@ function getDashboardJs() {
|
|
|
412
418
|
const xhr = new XMLHttpRequest();
|
|
413
419
|
xhr.open('GET', '/api/projects/' + projectId + '/state');
|
|
414
420
|
xhr.onload = () => {
|
|
415
|
-
if (xhr.status === 200 && activeTabId === projectId) {
|
|
421
|
+
if (xhr.status === 200 && ui.activeTabId === projectId) {
|
|
416
422
|
try {
|
|
417
423
|
const pState = JSON.parse(xhr.responseText);
|
|
418
424
|
state.branches = pState.branches || [];
|
|
@@ -435,20 +441,20 @@ function getDashboardJs() {
|
|
|
435
441
|
}
|
|
436
442
|
|
|
437
443
|
function switchTab(projectId) {
|
|
438
|
-
if (projectId === activeTabId) return;
|
|
439
|
-
activeTabId = projectId;
|
|
440
|
-
selectedIndex = 0;
|
|
441
|
-
searchQuery = '';
|
|
442
|
-
searchMode = false;
|
|
444
|
+
if (projectId === ui.activeTabId) return;
|
|
445
|
+
ui.activeTabId = projectId;
|
|
446
|
+
ui.selectedIndex = 0;
|
|
447
|
+
ui.searchQuery = '';
|
|
448
|
+
ui.searchMode = false;
|
|
443
449
|
document.getElementById('search-bar').className = 'search-bar';
|
|
444
450
|
document.getElementById('search-input').value = '';
|
|
445
451
|
renderTabs();
|
|
446
452
|
fetchAndApplyProjectState(projectId);
|
|
447
453
|
|
|
448
|
-
clearInterval(remoteTabPollTimer);
|
|
449
|
-
remoteTabPollTimer = null;
|
|
454
|
+
clearInterval(ui.remoteTabPollTimer);
|
|
455
|
+
ui.remoteTabPollTimer = null;
|
|
450
456
|
if (state && projectId !== state.activeProjectId) {
|
|
451
|
-
remoteTabPollTimer = setInterval(() => {
|
|
457
|
+
ui.remoteTabPollTimer = setInterval(() => {
|
|
452
458
|
fetchAndApplyProjectState(projectId);
|
|
453
459
|
}, 2000);
|
|
454
460
|
}
|
|
@@ -464,7 +470,7 @@ ${pureFnBlock}
|
|
|
464
470
|
getDisplayBranches = function() {
|
|
465
471
|
if (!state || !state.branches) return [];
|
|
466
472
|
return _pureGetDisplayBranches(state.branches, {
|
|
467
|
-
searchQuery: searchQuery,
|
|
473
|
+
searchQuery: ui.searchQuery,
|
|
468
474
|
pinnedBranches: pinnedBranches,
|
|
469
475
|
sortOrder: sortOrder,
|
|
470
476
|
});
|
|
@@ -487,7 +493,7 @@ ${pureFnBlock}
|
|
|
487
493
|
if (state.version) versionEl.textContent = 'v' + state.version;
|
|
488
494
|
|
|
489
495
|
// Status badge
|
|
490
|
-
if (connected) {
|
|
496
|
+
if (ui.connected) {
|
|
491
497
|
const badge = document.getElementById('status-badge');
|
|
492
498
|
if (state.isOffline) {
|
|
493
499
|
badge.className = 'badge badge-offline';
|
|
@@ -507,13 +513,13 @@ ${pureFnBlock}
|
|
|
507
513
|
renderPrefsBar();
|
|
508
514
|
|
|
509
515
|
// Auto-show update notification (once per session)
|
|
510
|
-
if (state.updateAvailable && !updateNotificationShown && !anyModalOpen()) {
|
|
511
|
-
updateNotificationShown = true;
|
|
516
|
+
if (state.updateAvailable && !ui.updateNotificationShown && !anyModalOpen()) {
|
|
517
|
+
ui.updateNotificationShown = true;
|
|
512
518
|
showUpdateModal();
|
|
513
519
|
}
|
|
514
520
|
|
|
515
521
|
// Update log viewer if open
|
|
516
|
-
if (logViewerMode) renderLogViewer();
|
|
522
|
+
if (ui.logViewerMode) renderLogViewer();
|
|
517
523
|
}
|
|
518
524
|
|
|
519
525
|
function renderBranches() {
|
|
@@ -522,14 +528,14 @@ ${pureFnBlock}
|
|
|
522
528
|
const countEl = document.getElementById('branch-count');
|
|
523
529
|
countEl.textContent = branches.length;
|
|
524
530
|
|
|
525
|
-
if (selectedIndex >= branches.length) {
|
|
526
|
-
selectedIndex = Math.max(0, branches.length - 1);
|
|
531
|
+
if (ui.selectedIndex >= branches.length) {
|
|
532
|
+
ui.selectedIndex = Math.max(0, branches.length - 1);
|
|
527
533
|
}
|
|
528
534
|
|
|
529
535
|
if (branches.length === 0) {
|
|
530
536
|
container.innerHTML = '<div class="empty-state">' +
|
|
531
537
|
'<div class="empty-state-icon">🌿</div>' +
|
|
532
|
-
(searchQuery ? 'No branches matching "' + escHtml(searchQuery) + '"' : 'No branches found') +
|
|
538
|
+
(ui.searchQuery ? 'No branches matching "' + escHtml(ui.searchQuery) + '"' : 'No branches found') +
|
|
533
539
|
'</div>';
|
|
534
540
|
return;
|
|
535
541
|
}
|
|
@@ -537,7 +543,7 @@ ${pureFnBlock}
|
|
|
537
543
|
let html = '';
|
|
538
544
|
for (let i = 0; i < branches.length; i++) {
|
|
539
545
|
const b = branches[i];
|
|
540
|
-
const isSelected = i === selectedIndex;
|
|
546
|
+
const isSelected = i === ui.selectedIndex;
|
|
541
547
|
const isCurrent = b.name === state.currentBranch;
|
|
542
548
|
|
|
543
549
|
// Sparkline
|
|
@@ -668,8 +674,8 @@ ${pureFnBlock}
|
|
|
668
674
|
|
|
669
675
|
// ── Log Viewer ─────────────────────────────────────────────────
|
|
670
676
|
function showLogViewer() {
|
|
671
|
-
logViewerMode = true;
|
|
672
|
-
logViewerTab = 'server';
|
|
677
|
+
ui.logViewerMode = true;
|
|
678
|
+
ui.logViewerTab = 'server';
|
|
673
679
|
renderLogViewer();
|
|
674
680
|
logViewerModal.show();
|
|
675
681
|
}
|
|
@@ -682,11 +688,11 @@ ${pureFnBlock}
|
|
|
682
688
|
// Update tab active state
|
|
683
689
|
const tabs = document.querySelectorAll('.log-viewer-tab');
|
|
684
690
|
for (let t = 0; t < tabs.length; t++) {
|
|
685
|
-
tabs[t].className = 'log-viewer-tab' + (tabs[t].getAttribute('data-tab') === logViewerTab ? ' active' : '');
|
|
691
|
+
tabs[t].className = 'log-viewer-tab' + (tabs[t].getAttribute('data-tab') === ui.logViewerTab ? ' active' : '');
|
|
686
692
|
}
|
|
687
693
|
|
|
688
694
|
let html = '';
|
|
689
|
-
if (logViewerTab === 'server') {
|
|
695
|
+
if (ui.logViewerTab === 'server') {
|
|
690
696
|
const logs = state.serverLogBuffer || [];
|
|
691
697
|
if (logs.length === 0) {
|
|
692
698
|
html = '<div style="color:var(--text-muted);padding:20px;text-align:center;">No server logs</div>';
|
|
@@ -721,16 +727,16 @@ ${pureFnBlock}
|
|
|
721
727
|
document.getElementById('log-viewer-tabs').addEventListener('click', (e) => {
|
|
722
728
|
const tab = e.target.closest('.log-viewer-tab');
|
|
723
729
|
if (!tab) return;
|
|
724
|
-
logViewerTab = tab.getAttribute('data-tab');
|
|
730
|
+
ui.logViewerTab = tab.getAttribute('data-tab');
|
|
725
731
|
renderLogViewer();
|
|
726
732
|
});
|
|
727
733
|
|
|
728
734
|
// ── Branch Action Modal ────────────────────────────────────────
|
|
729
735
|
function showBranchActions() {
|
|
730
736
|
const branches = getDisplayBranches();
|
|
731
|
-
if (!branches.length || selectedIndex >= branches.length) return;
|
|
732
|
-
const branch = branches[selectedIndex];
|
|
733
|
-
branchActionMode = true;
|
|
737
|
+
if (!branches.length || ui.selectedIndex >= branches.length) return;
|
|
738
|
+
const branch = branches[ui.selectedIndex];
|
|
739
|
+
ui.branchActionMode = true;
|
|
734
740
|
branchActionModal.show();
|
|
735
741
|
document.getElementById('branch-action-title').textContent = 'Actions: ' + branch.name;
|
|
736
742
|
|
|
@@ -836,7 +842,7 @@ ${pureFnBlock}
|
|
|
836
842
|
// ── Info Panel ─────────────────────────────────────────────────
|
|
837
843
|
function showInfo() {
|
|
838
844
|
if (!state) return;
|
|
839
|
-
infoMode = true;
|
|
845
|
+
ui.infoMode = true;
|
|
840
846
|
const grid = document.getElementById('info-grid');
|
|
841
847
|
const rows = [
|
|
842
848
|
['Project', state.projectName || '-'],
|
|
@@ -863,8 +869,8 @@ ${pureFnBlock}
|
|
|
863
869
|
|
|
864
870
|
// ── Stash Management ───────────────────────────────────────────
|
|
865
871
|
function showStashDialog(pendingBranch) {
|
|
866
|
-
stashMode = true;
|
|
867
|
-
pendingStashBranch = pendingBranch || null;
|
|
872
|
+
ui.stashMode = true;
|
|
873
|
+
ui.pendingStashBranch = pendingBranch || null;
|
|
868
874
|
const msg = pendingBranch
|
|
869
875
|
? 'You have uncommitted changes. Stash them before switching to <strong>' + escHtml(pendingBranch) + '</strong>?'
|
|
870
876
|
: 'Stash all uncommitted changes in the working directory?';
|
|
@@ -877,7 +883,7 @@ ${pureFnBlock}
|
|
|
877
883
|
stashModal.show();
|
|
878
884
|
document.getElementById('stash-cancel').onclick = hideStash;
|
|
879
885
|
document.getElementById('stash-confirm').onclick = () => {
|
|
880
|
-
sendAction('stash', { pendingBranch: pendingStashBranch });
|
|
886
|
+
sendAction('stash', { pendingBranch: ui.pendingStashBranch });
|
|
881
887
|
showToast('Stashing changes...', 'info');
|
|
882
888
|
hideStash();
|
|
883
889
|
};
|
|
@@ -887,7 +893,7 @@ ${pureFnBlock}
|
|
|
887
893
|
|
|
888
894
|
// ── Branch Cleanup ─────────────────────────────────────────────
|
|
889
895
|
function showCleanup() {
|
|
890
|
-
cleanupMode = true;
|
|
896
|
+
ui.cleanupMode = true;
|
|
891
897
|
const html = '<div style="color:var(--text-dim);font-size:13px;margin-bottom:12px;">Scanning for branches with deleted remotes...</div>';
|
|
892
898
|
document.getElementById('cleanup-content').innerHTML = html;
|
|
893
899
|
cleanupModal.show();
|
|
@@ -950,7 +956,7 @@ ${pureFnBlock}
|
|
|
950
956
|
// ── Update Notification ────────────────────────────────────────
|
|
951
957
|
function showUpdateModal() {
|
|
952
958
|
if (!state || !state.updateAvailable) return;
|
|
953
|
-
updateMode = true;
|
|
959
|
+
ui.updateMode = true;
|
|
954
960
|
const html = '<div class="update-versions">';
|
|
955
961
|
html += '<span class="old-version">v' + escHtml(state.version || '?') + '</span>';
|
|
956
962
|
html += '<span class="arrow">→</span>';
|
|
@@ -1029,7 +1035,7 @@ ${pureFnBlock}
|
|
|
1029
1035
|
hintEl.addEventListener('click', (e) => {
|
|
1030
1036
|
const h = e.currentTarget.getAttribute('data-hint');
|
|
1031
1037
|
if (h === 'Press S to stash') {
|
|
1032
|
-
showStashDialog(pendingStashBranch);
|
|
1038
|
+
showStashDialog(ui.pendingStashBranch);
|
|
1033
1039
|
}
|
|
1034
1040
|
toast.classList.remove('visible');
|
|
1035
1041
|
setTimeout(() => { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 300);
|
|
@@ -1056,10 +1062,10 @@ ${pureFnBlock}
|
|
|
1056
1062
|
}
|
|
1057
1063
|
|
|
1058
1064
|
// Log viewer tab switching
|
|
1059
|
-
if (logViewerMode) {
|
|
1065
|
+
if (ui.logViewerMode) {
|
|
1060
1066
|
if (e.key === 'Tab' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
|
1061
1067
|
e.preventDefault();
|
|
1062
|
-
logViewerTab = logViewerTab === 'server' ? 'activity' : 'server';
|
|
1068
|
+
ui.logViewerTab = ui.logViewerTab === 'server' ? 'activity' : 'server';
|
|
1063
1069
|
renderLogViewer();
|
|
1064
1070
|
}
|
|
1065
1071
|
return;
|
|
@@ -1069,11 +1075,11 @@ ${pureFnBlock}
|
|
|
1069
1075
|
if (_openModals.length > 0) return;
|
|
1070
1076
|
|
|
1071
1077
|
// Confirm dialog mode — Escape to cancel, Enter to confirm
|
|
1072
|
-
if (confirmMode) {
|
|
1078
|
+
if (ui.confirmMode) {
|
|
1073
1079
|
if (e.key === 'Escape') { e.preventDefault(); hideConfirm(); }
|
|
1074
1080
|
if (e.key === 'Enter') {
|
|
1075
1081
|
e.preventDefault();
|
|
1076
|
-
const cb = confirmCallback;
|
|
1082
|
+
const cb = ui.confirmCallback;
|
|
1077
1083
|
hideConfirm();
|
|
1078
1084
|
if (cb) cb();
|
|
1079
1085
|
}
|
|
@@ -1081,20 +1087,20 @@ ${pureFnBlock}
|
|
|
1081
1087
|
}
|
|
1082
1088
|
|
|
1083
1089
|
// Search mode
|
|
1084
|
-
if (searchMode) {
|
|
1090
|
+
if (ui.searchMode) {
|
|
1085
1091
|
if (e.key === 'Escape') {
|
|
1086
1092
|
e.preventDefault();
|
|
1087
|
-
searchMode = false;
|
|
1088
|
-
searchQuery = '';
|
|
1093
|
+
ui.searchMode = false;
|
|
1094
|
+
ui.searchQuery = '';
|
|
1089
1095
|
document.getElementById('search-bar').className = 'search-bar';
|
|
1090
1096
|
document.getElementById('search-input').value = '';
|
|
1091
|
-
selectedIndex = 0;
|
|
1097
|
+
ui.selectedIndex = 0;
|
|
1092
1098
|
renderBranches();
|
|
1093
1099
|
return;
|
|
1094
1100
|
}
|
|
1095
1101
|
if (e.key === 'Enter') {
|
|
1096
1102
|
e.preventDefault();
|
|
1097
|
-
searchMode = false;
|
|
1103
|
+
ui.searchMode = false;
|
|
1098
1104
|
document.getElementById('search-bar').className = 'search-bar';
|
|
1099
1105
|
return;
|
|
1100
1106
|
}
|
|
@@ -1125,7 +1131,7 @@ ${pureFnBlock}
|
|
|
1125
1131
|
// Tab cycling with Tab key
|
|
1126
1132
|
if (e.key === 'Tab' && projects.length > 1) {
|
|
1127
1133
|
e.preventDefault();
|
|
1128
|
-
const curIdx = projects.findIndex((p) => p.id === activeTabId);
|
|
1134
|
+
const curIdx = projects.findIndex((p) => p.id === ui.activeTabId);
|
|
1129
1135
|
const nextIdx = e.shiftKey
|
|
1130
1136
|
? (curIdx - 1 + projects.length) % projects.length
|
|
1131
1137
|
: (curIdx + 1) % projects.length;
|
|
@@ -1148,8 +1154,8 @@ ${pureFnBlock}
|
|
|
1148
1154
|
case 'Enter':
|
|
1149
1155
|
e.preventDefault();
|
|
1150
1156
|
const branches = getDisplayBranches();
|
|
1151
|
-
if (branches.length > 0 && selectedIndex < branches.length) {
|
|
1152
|
-
const b = branches[selectedIndex];
|
|
1157
|
+
if (branches.length > 0 && ui.selectedIndex < branches.length) {
|
|
1158
|
+
const b = branches[ui.selectedIndex];
|
|
1153
1159
|
if (b.isDeleted) {
|
|
1154
1160
|
showToast('Cannot switch to a deleted branch', 'error');
|
|
1155
1161
|
} else if (b.name === state.currentBranch) {
|
|
@@ -1162,9 +1168,9 @@ ${pureFnBlock}
|
|
|
1162
1168
|
break;
|
|
1163
1169
|
case '/':
|
|
1164
1170
|
e.preventDefault();
|
|
1165
|
-
searchMode = true;
|
|
1166
|
-
searchQuery = '';
|
|
1167
|
-
selectedIndex = 0;
|
|
1171
|
+
ui.searchMode = true;
|
|
1172
|
+
ui.searchQuery = '';
|
|
1173
|
+
ui.selectedIndex = 0;
|
|
1168
1174
|
document.getElementById('search-bar').className = 'search-bar active';
|
|
1169
1175
|
const input = document.getElementById('search-input');
|
|
1170
1176
|
input.value = '';
|
|
@@ -1259,16 +1265,16 @@ ${pureFnBlock}
|
|
|
1259
1265
|
|
|
1260
1266
|
// Search input handler
|
|
1261
1267
|
document.getElementById('search-input').addEventListener('input', (e) => {
|
|
1262
|
-
searchQuery = e.target.value;
|
|
1263
|
-
selectedIndex = 0;
|
|
1268
|
+
ui.searchQuery = e.target.value;
|
|
1269
|
+
ui.selectedIndex = 0;
|
|
1264
1270
|
renderBranches();
|
|
1265
1271
|
});
|
|
1266
1272
|
|
|
1267
1273
|
function moveSelection(delta) {
|
|
1268
1274
|
const branches = getDisplayBranches();
|
|
1269
|
-
const newIndex = selectedIndex + delta;
|
|
1275
|
+
const newIndex = ui.selectedIndex + delta;
|
|
1270
1276
|
if (newIndex >= 0 && newIndex < branches.length) {
|
|
1271
|
-
selectedIndex = newIndex;
|
|
1277
|
+
ui.selectedIndex = newIndex;
|
|
1272
1278
|
renderBranches();
|
|
1273
1279
|
}
|
|
1274
1280
|
}
|
|
@@ -1279,7 +1285,7 @@ ${pureFnBlock}
|
|
|
1279
1285
|
if (!item) return;
|
|
1280
1286
|
const idx = parseInt(item.getAttribute('data-index'), 10);
|
|
1281
1287
|
if (isNaN(idx)) return;
|
|
1282
|
-
selectedIndex = idx;
|
|
1288
|
+
ui.selectedIndex = idx;
|
|
1283
1289
|
renderBranches();
|
|
1284
1290
|
|
|
1285
1291
|
// Double-click to switch with confirmation
|
|
@@ -1339,8 +1345,8 @@ ${pureFnBlock}
|
|
|
1339
1345
|
}
|
|
1340
1346
|
if (e.target.id === 'pin-selected-btn') {
|
|
1341
1347
|
const branches = getDisplayBranches();
|
|
1342
|
-
if (branches.length > 0 && selectedIndex < branches.length) {
|
|
1343
|
-
const bn = branches[selectedIndex].name;
|
|
1348
|
+
if (branches.length > 0 && ui.selectedIndex < branches.length) {
|
|
1349
|
+
const bn = branches[ui.selectedIndex].name;
|
|
1344
1350
|
const idx = pinnedBranches.indexOf(bn);
|
|
1345
1351
|
if (idx === -1) {
|
|
1346
1352
|
pinnedBranches.push(bn);
|