-
Notifications
You must be signed in to change notification settings - Fork 601
feat: add keyboard shortcuts for search focus and section navigation #1992
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
950c3bb
bc713fa
9f8ca59
3580b61
53c56e2
3d52d6a
127901e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1275,7 +1275,7 @@ | |
| </header> | ||
|
|
||
| <!-- Mobile Menu Overlay --> | ||
| <div id="mobileMenu" class="fixed inset-0 z-50 hidden md:hidden" style="z-index:60;" role="dialog" aria-modal="true" aria-label="Navigation menu"> | ||
|
Check warning on line 1278 in index.html
|
||
| <!-- Backdrop --> | ||
| <div class="absolute inset-0 bg-black/60 backdrop-blur-sm" onclick="toggleMenu()" onkeydown="if(event.key==='Enter'||event.key===' ')toggleMenu()" tabindex="0" role="button" aria-label="Close menu"></div> | ||
|
|
||
|
|
@@ -1356,6 +1356,7 @@ | |
| <div class="relative z-10 bg-white dark:bg-[#18181b] rounded-[calc(1rem-1.5px)] w-full search-border-inner flex items-center gap-2 transition-all duration-300"> | ||
| <span class="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-zinc-400">search</span> | ||
| <input id="hero-search" type="text" placeholder="Search organization names..." aria-label="Search GSoC 2026 organizations" class="flex-1 bg-transparent border-0 rounded-2xl py-3 sm:py-4 pl-12 pr-4 text-sm font-medium focus:ring-0 focus:outline-none transition-all text-zinc-900 dark:text-zinc-100" /> | ||
| <span class="hidden sm:inline-flex items-center gap-0.5 mr-2 px-1.5 py-0.5 rounded-md border border-zinc-300 dark:border-zinc-600 text-[10px] font-mono font-semibold text-zinc-400 dark:text-zinc-500 select-none" aria-hidden="true">β/Ctrl K</span> | ||
| <button id="hero-search-submit" type="button" class="mr-2 inline-flex items-center justify-center rounded-xl bg-primary px-4 py-2 text-xs sm:text-sm font-bold text-white transition-colors hover:bg-orange-600 focus:outline-none focus:ring-2 focus:ring-primary/30"> | ||
| Search | ||
| </button> | ||
|
|
@@ -5199,7 +5200,7 @@ | |
| } | ||
| </script> | ||
|
|
||
| <div class="modal-bg" id="helpModal"> | ||
| <div class="modal-bg" id="helpModal" role="dialog" aria-modal="true" aria-labelledby="helpModalTitle"> | ||
|
Check warning on line 5203 in index.html
|
||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| <div class="modal"> | ||
|
|
||
| <button class="close-btn" onclick="closeHelpModal()" aria-label="Close keyboard shortcuts modal"> | ||
|
|
@@ -5209,7 +5210,7 @@ | |
|
|
||
|
|
||
| <div class="modal-header"> | ||
| <h2>Keyboard Shortcuts</h2> | ||
| <h2 id="helpModalTitle">Keyboard Shortcuts</h2> | ||
| <p>Quick shortcuts for navigation</p> | ||
| </div> | ||
|
|
||
|
|
@@ -5245,10 +5246,20 @@ | |
| </tr> | ||
|
|
||
| <tr class="shortcut-row"> | ||
| <td class="py-2"><kbd>/</kbd></td> | ||
| <td class="py-2"><kbd>/</kbd> or <kbd>β</kbd><kbd>K</kbd></td> | ||
| <td class="py-2">Focus search bar</td> | ||
| </tr> | ||
|
|
||
| <tr class="shortcut-row"> | ||
| <td class="py-2"><kbd>Alt</kbd><kbd>1</kbd></td> | ||
| <td class="py-2">Jump to Timeline section</td> | ||
| </tr> | ||
|
|
||
| <tr class="shortcut-row"> | ||
| <td class="py-2"><kbd>Alt</kbd><kbd>2</kbd></td> | ||
| <td class="py-2">Jump to Organizations section</td> | ||
| </tr> | ||
|
|
||
|
|
||
| <tr class="category-row"> | ||
| <td colspan="2">Actions</td> | ||
|
|
@@ -5309,16 +5320,38 @@ | |
|
|
||
| const helpModal = document.getElementById('helpModal'); | ||
| const helpBtn = document.getElementById('helpBtn'); | ||
| let helpModalTriggerEl = null; | ||
|
|
||
| function trapHelpModalFocus(e) { | ||
| if (e.key !== 'Tab' || !helpModal) return; | ||
| const focusables = helpModal.querySelectorAll('button, [href], input, select, textarea, [tabindex="0"]'); | ||
| if (!focusables.length) return; | ||
| const first = focusables[0]; | ||
| const last = focusables[focusables.length - 1]; | ||
| if (e.shiftKey && document.activeElement === first) { | ||
| last.focus(); | ||
| e.preventDefault(); | ||
| } else if (!e.shiftKey && document.activeElement === last) { | ||
| first.focus(); | ||
| e.preventDefault(); | ||
| } | ||
| } | ||
|
|
||
| function openHelpModal() { | ||
| if (helpModal) { | ||
| helpModalTriggerEl = document.activeElement; | ||
| helpModal.classList.add('open'); | ||
| helpModal.querySelector('.close-btn')?.focus(); | ||
| document.addEventListener('keydown', trapHelpModalFocus); | ||
| } | ||
| } | ||
|
|
||
| function closeHelpModal() { | ||
| if (helpModal) { | ||
| helpModal.classList.remove('open'); | ||
| document.removeEventListener('keydown', trapHelpModalFocus); | ||
| helpModalTriggerEl?.focus(); | ||
| helpModalTriggerEl = null; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -5326,30 +5359,46 @@ | |
| helpBtn.addEventListener('click', openHelpModal); | ||
| } | ||
|
|
||
| document.addEventListener('keydown', (e) => { | ||
|
Check failure on line 5362 in index.html
|
||
| const active = document.activeElement; | ||
| const isTyping = active && ( | ||
| ['INPUT', 'TEXTAREA', 'SELECT'].includes(active.tagName) || | ||
| active.id === 'searchInput' | ||
| ); | ||
|
|
||
| const helpModalOpen = | ||
| document.getElementById('helpModal')?.classList.contains('open'); | ||
| const orgModalOpen = | ||
| document.getElementById('orgModal')?.classList.contains('open'); | ||
| const proposalModalOpen = | ||
| document.getElementById('proposalModal')?.classList.contains('open'); | ||
|
Comment on lines
+5369
to
+5374
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
| const anyModalOpen = helpModalOpen || orgModalOpen || proposalModalOpen; | ||
| // Focus search bar β works alongside Ctrl/Cmd but not while already typing or a modal is open | ||
| if (!anyModalOpen && !isTyping && (e.key === '/' || ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k'))) { | ||
| e.preventDefault(); | ||
| document.getElementById('hero-search')?.focus(); | ||
| return; | ||
| } | ||
| // Jump to Timeline section | ||
| if (!anyModalOpen && !isTyping && e.altKey && e.key === '1') { | ||
| e.preventDefault(); | ||
| document.getElementById('timeline')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); | ||
| return; | ||
| } | ||
| // Jump to Organizations section | ||
| if (!anyModalOpen && !isTyping && e.altKey && e.key === '2') { | ||
| e.preventDefault(); | ||
| document.getElementById('orgs')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); | ||
| return; | ||
| } | ||
|
|
||
| if (e.ctrlKey || e.metaKey || e.altKey) { | ||
| return; | ||
| } | ||
| const active = document.activeElement; | ||
| if ( | ||
| active && | ||
| ( | ||
| ['INPUT', 'TEXTAREA', 'SELECT'].includes(active.tagName) || | ||
| active.id === 'searchInput' | ||
| ) | ||
| ) { | ||
| if (isTyping) { | ||
|
Comment on lines
+5363
to
+5398
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π Maintainability & Code Quality | π Major | β‘ Quick win π§© Analysis chainπ Script executed: #!/bin/bash
# Check for duplicate keydown listeners across index.html and app.js
rg -n "addEventListener\('keydown'|onkeydown" index.html src/js/app.jsRepository: S3DFX-CYBER/GSoC-Org-Finder- Length of output: 1193 π Script executed: #!/bin/bash
set -euo pipefail
# Inspect the global keydown handler and the inline handler around the relevant lines.
sed -n '720,750p' src/js/app.js
printf '\n--- index.html ---\n'
sed -n '5338,5375p' index.htmlRepository: S3DFX-CYBER/GSoC-Org-Finder- Length of output: 2207 Consolidate the global keydown shortcut handler This inline π§° Toolsπͺ ast-grep (0.44.0)[warning] 5341-5344: Avoid SQL injections (variable-sql-statement-injection) [warning] 5342-5343: Avoid SQL injections (variable-sql-statement-injection) π€ Prompt for AI Agents |
||
| return; | ||
| } | ||
| const helpModalOpen = | ||
| document.getElementById('helpModal')?.classList.contains('open'); | ||
|
|
||
| const orgModalOpen = | ||
| document.getElementById('orgModal')?.classList.contains('open'); | ||
|
|
||
| const proposalModalOpen = | ||
| document.getElementById('proposalModal')?.classList.contains('open'); | ||
|
|
||
| if (helpModalOpen || orgModalOpen || proposalModalOpen) { | ||
| if (anyModalOpen && e.key !== 'Escape') { | ||
| return; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -664,7 +664,7 @@ | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Global keydown helper functions to manage Cognitive Complexity | ||||||||||||||||||||||
| function handleEscapeKey(e) { | ||||||||||||||||||||||
| const activeModal = document.querySelector('.modal-bg.open, #mobileMenu:not(.hidden), .modal-bg.compare-bg.open'); | ||||||||||||||||||||||
| const activeModal = document.querySelector('.modal-bg.open, #mobileMenu:not(.hidden), .modal-bg.compare-bg.open, #proposalModal.open, #proposalModal[open]'); | ||||||||||||||||||||||
| if (activeModal) { | ||||||||||||||||||||||
| e.preventDefault(); | ||||||||||||||||||||||
| closeModalElement(activeModal.id); | ||||||||||||||||||||||
|
|
@@ -713,20 +713,35 @@ | |||||||||||||||||||||
| updateCardFocus(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| function handleGlobalKeydown(e) { | ||||||||||||||||||||||
|
Check failure on line 716 in src/js/app.js
|
||||||||||||||||||||||
| if (e.key === 'Escape' && handleEscapeKey(e)) return; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||
| if (['INPUT', 'SELECT', 'TEXTAREA'].includes(document.activeElement?.tagName)) return; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const anyModalOpen = !!document.querySelector('.modal-bg.open, .modal-bg.compare-bg.open, #proposalModal.open, #proposalModal[open]'); | ||||||||||||||||||||||
|
Check warning on line 721 in src/js/app.js
|
||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The Prompt for AI agents |
||||||||||||||||||||||
| const anyModalOpen = !!document.querySelector('.modal-bg.open, .modal-bg.compare-bg.open, #proposalModal.open, #proposalModal[open]'); | ||||||||||||||||||||||
|
Check warning on line 722 in src/js/app.js
|
||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P0: There are duplicate Prompt for AI agents |
||||||||||||||||||||||
| if (anyModalOpen) return; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const n = filteredOrgs.length; | ||||||||||||||||||||||
|
Check warning on line 725 in src/js/app.js
|
||||||||||||||||||||||
| const n = filteredOrgs.length; | ||||||||||||||||||||||
|
greptile-apps[bot] marked this conversation as resolved.
Comment on lines
+721
to
726
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
| if (e.key === '?') { | ||||||||||||||||||||||
| if (!anyModalOpen && e.key === '?') { | ||||||||||||||||||||||
| e.preventDefault(); | ||||||||||||||||||||||
| openModalElement('helpModal'); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if (e.key === '/') { | ||||||||||||||||||||||
| if (!anyModalOpen && (e.key === '/' || ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k'))) { | ||||||||||||||||||||||
| e.preventDefault(); | ||||||||||||||||||||||
| document.getElementById('hero-search')?.focus(); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if (!anyModalOpen && e.altKey && e.key === '1') { | ||||||||||||||||||||||
| e.preventDefault(); | ||||||||||||||||||||||
| document.getElementById('timeline')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if (!anyModalOpen && e.altKey && e.key === '2') { | ||||||||||||||||||||||
| e.preventDefault(); | ||||||||||||||||||||||
| document.getElementById('searchInput')?.focus(); | ||||||||||||||||||||||
| document.getElementById('orgs')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.