swadhinbiswas

[email protected]
2026-01-18 22:49:31 +0600 committed NaN years ago
5a622bc
commit 5a622bc
2 files changed
+224 -1
▼src/lib/inbox-shortcuts.ts
+93-0
1
+/**
2
+ * Inbox Keyboard Shortcuts
3
+ * Graphite-style keyboard navigation for PR inbox
4
+ */
5
+
6
+// Keyboard shortcut handler
7
+export function initInboxKeyboardShortcuts() {
8
+ let selectedIndex = 0;
9
+ const prItems = document.querySelectorAll('[data-pr-item]');
10
+
11
+ if (prItems.length === 0) return;
12
+
13
+ // Highlight the first item
14
+ const highlightPR = (index: number) => {
15
+ prItems.forEach((item, i) => {
16
+ if (i === index) {
17
+ item.classList.add('ring-2', 'ring-primary', 'bg-primary/5');
18
+ item.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
19
+ } else {
20
+ item.classList.remove('ring-2', 'ring-primary', 'bg-primary/5');
21
+ }
22
+ });
23
+ };
24
+
25
+ // Initialize
26
+ highlightPR(0);
27
+
28
+ // Add keyboard listener
29
+ document.addEventListener('keydown', (e) => {
30
+ // Don't trigger if user is typing in an input
31
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
32
+ return;
33
+ }
34
+
35
+ switch (e.key.toLowerCase()) {
36
+ case 'j': // Next PR
37
+ e.preventDefault();
38
+ selectedIndex = Math.min(selectedIndex + 1, prItems.length - 1);
39
+ highlightPR(selectedIndex);
40
+ break;
41
+
42
+ case 'k': // Previous PR
43
+ e.preventDefault();
44
+ selectedIndex = Math.max(selectedIndex - 1, 0);
45
+ highlightPR(selectedIndex);
46
+ break;
47
+
48
+ case 'o': // Open PR
49
+ case 'enter':
50
+ e.preventDefault();
51
+ const selectedItem = prItems[selectedIndex] as HTMLAnchorElement;
52
+ selectedItem?.click();
53
+ break;
54
+
55
+ case 'r': // Refresh
56
+ e.preventDefault();
57
+ window.location.reload();
58
+ break;
59
+
60
+ case '?': // Show help
61
+ e.preventDefault();
62
+ toggleHelpModal();
63
+ break;
64
+
65
+ case 'escape':
66
+ e.preventDefault();
67
+ closeHelpModal();
68
+ break;
69
+ }
70
+ });
71
+
72
+ // Help modal
73
+ function toggleHelpModal() {
74
+ const modal = document.getElementById('keyboard-shortcuts-modal');
75
+ if (modal) {
76
+ modal.classList.toggle('hidden');
77
+ }
78
+ }
79
+
80
+ function closeHelpModal() {
81
+ const modal = document.getElementById('keyboard-shortcuts-modal');
82
+ if (modal) {
83
+ modal.classList.add('hidden');
84
+ }
85
+ }
86
+
87
+ console.log('✅ Inbox keyboard shortcuts active');
88
+}
89
+
90
+// Auto-init on page load
91
+if (typeof window !== 'undefined') {
92
+ document.addEventListener('DOMContentLoaded', initInboxKeyboardShortcuts);
93
+}
0
94
▼src/pages/inbox.astro
+131-1
255
255
) : (
256
256
prsWithStacks.map((pr) => (
257
257
<a
258
+ data-pr-item
258
259
href={`/${pr.repository?.name}/pulls/${pr.number}`}
259
- class="flex items-start gap-4 p-4 rounded-lg border transition-all hover:bg-muted/50 hover:shadow-md group"
260
+ class="flex items-start gap-4 p-4 rounded-lg border transition-all hover:bg-muted/50 hover:shadow-md group cursor-pointer"
260
261
>
261
262
{/* PR Icon */}
262
263
<div
320
321
</div>
321
322
</div>
322
323
</div>
324
+
325
+ <!-- Keyboard Shortcuts Help Modal -->
326
+ <div
327
+ id="keyboard-shortcuts-modal"
328
+ class="hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50"
329
+ >
330
+ <div
331
+ class="bg-background border rounded-lg shadow-lg p-6 max-w-md w-full mx-4"
332
+ >
333
+ <h3 class="text-lg font-semibold mb-4">Keyboard Shortcuts</h3>
334
+ <div class="space-y-2 text-sm">
335
+ <div class="flex justify-between">
336
+ <kbd class="px-2 py-1 bg-muted rounded text-xs">j</kbd>
337
+ <span>Next PR</span>
338
+ </div>
339
+ <div class="flex justify-between">
340
+ <kbd class="px-2 py-1 bg-muted rounded text-xs">k</kbd>
341
+ <span>Previous PR</span>
342
+ </div>
343
+ <div class="flex justify-between">
344
+ <kbd class="px-2 py-1 bg-muted rounded text-xs">o</kbd>
345
+ <span>Open selected PR</span>
346
+ </div>
347
+ <div class="flex justify-between">
348
+ <kbd class="px-2 py-1 bg-muted rounded text-xs">r</kbd>
349
+ <span>Refresh</span>
350
+ </div>
351
+ <div class="flex justify-between">
352
+ <kbd class="px-2 py-1 bg-muted rounded text-xs">?</kbd>
353
+ <span>Show this help</span>
354
+ </div>
355
+ <div class="flex justify-between">
356
+ <kbd class="px-2 py-1 bg-muted rounded text-xs">Esc</kbd>
357
+ <span>Close dialog</span>
358
+ </div>
359
+ </div>
360
+ <button
361
+ onclick="document.getElementById('keyboard-shortcuts-modal').classList.add('hidden')"
362
+ class="mt-4 w-full px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90"
363
+ >
364
+ Got it
365
+ </button>
366
+ </div>
367
+ </div>
368
+
369
+ <!-- Keyboard hint -->
370
+ <div
371
+ class="fixed bottom-4 right-4 text-xs text-muted-foreground bg-background border rounded-lg px-3 py-2 shadow-lg"
372
+ >
373
+ Press <kbd class="px-1.5 py-0.5 bg-muted rounded text-xs mx-0.5">?</kbd> for keyboard
374
+ shortcuts
375
+ </div>
376
+
377
+ <script>
378
+ // Keyboard shortcut handler
379
+ function initInboxKeyboardShortcuts() {
380
+ let selectedIndex = 0;
381
+ const prItems = document.querySelectorAll("[data-pr-item]");
382
+
383
+ if (prItems.length === 0) return;
384
+
385
+ // Highlight the first item
386
+ const highlightPR = (index) => {
387
+ prItems.forEach((item, i) => {
388
+ if (i === index) {
389
+ item.classList.add("ring-2", "ring-primary", "bg-primary/5");
390
+ item.scrollIntoView({ block: "nearest", behavior: "smooth" });
391
+ } else {
392
+ item.classList.remove("ring-2", "ring-primary", "bg-primary/5");
393
+ }
394
+ });
395
+ };
396
+
397
+ // Initialize
398
+ highlightPR(0);
399
+
400
+ // Add keyboard listener
401
+ document.addEventListener("keydown", (e) => {
402
+ // Don't trigger if user is typing in an input
403
+ if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") {
404
+ return;
405
+ }
406
+
407
+ switch (e.key.toLowerCase()) {
408
+ case "j": // Next PR
409
+ e.preventDefault();
410
+ selectedIndex = Math.min(selectedIndex + 1, prItems.length - 1);
411
+ highlightPR(selectedIndex);
412
+ break;
413
+
414
+ case "k": // Previous PR
415
+ e.preventDefault();
416
+ selectedIndex = Math.max(selectedIndex - 1, 0);
417
+ highlightPR(selectedIndex);
418
+ break;
419
+
420
+ case "o": // Open PR
421
+ case "enter":
422
+ e.preventDefault();
423
+ prItems[selectedIndex]?.click();
424
+ break;
425
+
426
+ case "r": // Refresh
427
+ e.preventDefault();
428
+ window.location.reload();
429
+ break;
430
+
431
+ case "?": // Show help
432
+ e.preventDefault();
433
+ document
434
+ .getElementById("keyboard-shortcuts-modal")
435
+ ?.classList.toggle("hidden");
436
+ break;
437
+
438
+ case "escape":
439
+ e.preventDefault();
440
+ document
441
+ .getElementById("keyboard-shortcuts-modal")
442
+ ?.classList.add("hidden");
443
+ break;
444
+ }
445
+ });
446
+
447
+ console.log("✅ Inbox keyboard shortcuts active");
448
+ }
449
+
450
+ // Auto-init on page load
451
+ initInboxKeyboardShortcuts();
452
+ </script>
323
453
</BaseLayout>
324
454