-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
Add keyboard shortcuts for repository file and code search #36416
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
Open
micahkepe
wants to merge
5
commits into
go-gitea:main
Choose a base branch
from
micahkepe:feature/repo-keyboard-shortcuts
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+414
−4
Open
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
d19475e
feat(shortcut): Add keyboard shortcuts for repository file and code s…
micahkepe 33c2b20
refactor(shortcut): Use declarative data attributes for keyboard shor…
micahkepe 7f14c0a
fix(shortcut): Address PR review feedback
micahkepe bd469f0
feat(aria): add aria-keyshortcuts for file and repo search shortcuts
micahkepe bb2d4a8
Merge branch 'main' into feature/repo-keyboard-shortcuts
micahkepe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| // Copyright 2026 The Gitea Authors. All rights reserved. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| import {test, expect} from '@playwright/test'; | ||
| import {login_user, load_logged_in_context} from './utils_e2e.ts'; | ||
|
|
||
| test.beforeAll(async ({browser}, workerInfo) => { | ||
| await login_user(browser, workerInfo, 'user2'); | ||
| }); | ||
|
|
||
| test.describe('Repository Keyboard Shortcuts', () => { | ||
| test('T key focuses file search input', async ({browser}, workerInfo) => { | ||
| const context = await load_logged_in_context(browser, workerInfo, 'user2'); | ||
| const page = await context.newPage(); | ||
|
|
||
| // Navigate to a repository page with file listing | ||
micahkepe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| await page.goto('/user2/repo1'); | ||
| await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle | ||
|
|
||
| // Verify the file search input exists and has the keyboard hint | ||
| const fileSearchInput = page.locator('.repo-file-search-container input'); | ||
| await expect(fileSearchInput).toBeVisible(); | ||
|
|
||
| // Verify the keyboard hint is visible | ||
| const kbdHint = page.locator('.repo-file-search-input-wrapper kbd'); | ||
| await expect(kbdHint).toBeVisible(); | ||
| await expect(kbdHint).toHaveText('T'); | ||
|
|
||
| // Press T key to focus the file search input | ||
| await page.keyboard.press('t'); | ||
|
|
||
| // Verify the input is focused | ||
| await expect(fileSearchInput).toBeFocused(); | ||
| }); | ||
|
|
||
| test('T key does not trigger when typing in input', async ({browser}, workerInfo) => { | ||
| const context = await load_logged_in_context(browser, workerInfo, 'user2'); | ||
| const page = await context.newPage(); | ||
|
|
||
| // Navigate to a repository page | ||
| await page.goto('/user2/repo1'); | ||
| await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle | ||
|
|
||
| // Focus on file search first | ||
| const fileSearchInput = page.locator('.repo-file-search-container input'); | ||
| await fileSearchInput.click(); | ||
|
|
||
| // Type something including 't' | ||
| await page.keyboard.type('test'); | ||
|
|
||
| // Verify the input still has focus and contains the typed text | ||
| await expect(fileSearchInput).toBeFocused(); | ||
| await expect(fileSearchInput).toHaveValue('test'); | ||
| }); | ||
|
|
||
| test('S key focuses code search input on repo home', async ({browser}, workerInfo) => { | ||
| const context = await load_logged_in_context(browser, workerInfo, 'user2'); | ||
| const page = await context.newPage(); | ||
|
|
||
| // Navigate to repo home page where code search is available | ||
| await page.goto('/user2/repo1'); | ||
| await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle | ||
|
|
||
| // The code search input is in the sidebar | ||
| const codeSearchInput = page.locator('.code-search-input'); | ||
| await expect(codeSearchInput).toBeVisible(); | ||
|
|
||
| // Verify the keyboard hint is visible | ||
| const kbdHint = page.locator('.repo-code-search-input-wrapper .repo-search-shortcut-hint'); | ||
| await expect(kbdHint).toBeVisible(); | ||
| await expect(kbdHint).toHaveText('S'); | ||
|
|
||
| // Press S key to focus the code search input | ||
| await page.keyboard.press('s'); | ||
|
|
||
| // Verify the input is focused | ||
| await expect(codeSearchInput).toBeFocused(); | ||
| }); | ||
|
|
||
| test('File search keyboard hint hides when input has value', async ({browser}, workerInfo) => { | ||
| const context = await load_logged_in_context(browser, workerInfo, 'user2'); | ||
| const page = await context.newPage(); | ||
|
|
||
| // Navigate to a repository page | ||
| await page.goto('/user2/repo1'); | ||
| await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle | ||
|
|
||
| // Check file search kbd hint | ||
| const fileSearchInput = page.locator('.repo-file-search-container input'); | ||
| const fileKbdHint = page.locator('.repo-file-search-input-wrapper kbd'); | ||
|
|
||
| // Initially the hint should be visible | ||
| await expect(fileKbdHint).toBeVisible(); | ||
|
|
||
| // Focus and type in the file search | ||
| await fileSearchInput.click(); | ||
| await page.keyboard.type('test'); | ||
|
|
||
| // The hint should now be hidden (Vue component handles this with v-show) | ||
| await expect(fileKbdHint).toBeHidden(); | ||
| }); | ||
|
|
||
| test('Code search keyboard hint hides when input has value', async ({browser}, workerInfo) => { | ||
| const context = await load_logged_in_context(browser, workerInfo, 'user2'); | ||
| const page = await context.newPage(); | ||
|
|
||
| // Navigate to a repository page | ||
| await page.goto('/user2/repo1'); | ||
| await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle | ||
|
|
||
| const codeSearchInput = page.locator('.code-search-input'); | ||
| await expect(codeSearchInput).toBeVisible(); | ||
|
|
||
| const codeKbdHint = page.locator('.repo-code-search-input-wrapper .repo-search-shortcut-hint'); | ||
|
|
||
| // Initially the hint should be visible | ||
| await expect(codeKbdHint).toBeVisible(); | ||
|
|
||
| // Focus and type in the code search | ||
| await codeSearchInput.click(); | ||
| await page.keyboard.type('search'); | ||
|
|
||
| // The hint should now be hidden | ||
| await expect(codeKbdHint).toBeHidden(); | ||
| }); | ||
|
|
||
| test('Shortcuts do not trigger with modifier keys', async ({browser}, workerInfo) => { | ||
| const context = await load_logged_in_context(browser, workerInfo, 'user2'); | ||
| const page = await context.newPage(); | ||
|
|
||
| // Navigate to a repository page | ||
| await page.goto('/user2/repo1'); | ||
| await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle | ||
|
|
||
| const fileSearchInput = page.locator('.repo-file-search-container input'); | ||
|
|
||
| // Click somewhere else first to ensure nothing is focused | ||
| await page.locator('body').click(); | ||
|
|
||
| // Press Ctrl+T (should not focus file search - this is typically "new tab" in browsers) | ||
| await page.keyboard.press('Control+t'); | ||
|
|
||
| // The file search input should NOT be focused | ||
| await expect(fileSearchInput).not.toBeFocused(); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| // Copyright 2026 The Gitea Authors. All rights reserved. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| import {initRepoCodeSearchShortcut} from './repo-shortcuts.ts'; | ||
|
|
||
| describe('Repository Code Search Shortcut Hint', () => { | ||
| let codeSearchInput: HTMLInputElement; | ||
micahkepe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let codeSearchHint: HTMLElement; | ||
|
|
||
| beforeEach(() => { | ||
| // Set up DOM structure for code search | ||
| document.body.innerHTML = ` | ||
| <div class="repo-home-sidebar-top"> | ||
| <div class="repo-code-search-input-wrapper"> | ||
| <input name="q" class="code-search-input" placeholder="Search code" data-global-keyboard-shortcut="s" data-global-init="initRepoCodeSearchShortcut"> | ||
| <kbd class="repo-search-shortcut-hint">S</kbd> | ||
| </div> | ||
| </div> | ||
| `; | ||
|
|
||
| codeSearchInput = document.querySelector('.code-search-input')!; | ||
| codeSearchHint = document.querySelector('.repo-code-search-input-wrapper .repo-search-shortcut-hint')!; | ||
|
|
||
| // Initialize the shortcut hint functionality directly | ||
| initRepoCodeSearchShortcut(codeSearchInput); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| document.body.innerHTML = ''; | ||
| }); | ||
|
|
||
| test('Code search hint hides when input has value', () => { | ||
| // Initially visible | ||
| expect(codeSearchHint.style.display).toBe(''); | ||
|
|
||
| // Type something in the code search | ||
| codeSearchInput.value = 'test'; | ||
| codeSearchInput.dispatchEvent(new Event('input')); | ||
|
|
||
| // Should be hidden | ||
| expect(codeSearchHint.style.display).toBe('none'); | ||
| }); | ||
|
|
||
| test('Code search hint shows when input is cleared', () => { | ||
| // Set a value and trigger input | ||
| codeSearchInput.value = 'test'; | ||
| codeSearchInput.dispatchEvent(new Event('input')); | ||
| expect(codeSearchHint.style.display).toBe('none'); | ||
|
|
||
| // Clear the value | ||
| codeSearchInput.value = ''; | ||
| codeSearchInput.dispatchEvent(new Event('input')); | ||
|
|
||
| // Should be visible again | ||
| expect(codeSearchHint.style.display).toBe(''); | ||
| }); | ||
|
|
||
| test('Escape key clears and blurs code search input', () => { | ||
| // Set a value and focus the input | ||
| codeSearchInput.value = 'test'; | ||
| codeSearchInput.dispatchEvent(new Event('input')); | ||
| codeSearchInput.focus(); | ||
| expect(document.activeElement).toBe(codeSearchInput); | ||
| expect(codeSearchInput.value).toBe('test'); | ||
|
|
||
| // Press Escape directly on the input | ||
| const event = new KeyboardEvent('keydown', {key: 'Escape', bubbles: true}); | ||
| codeSearchInput.dispatchEvent(event); | ||
|
|
||
| // Value should be cleared and input should be blurred | ||
| expect(codeSearchInput.value).toBe(''); | ||
| expect(document.activeElement).not.toBe(codeSearchInput); | ||
| }); | ||
|
|
||
| test('Code search kbd hint hides on focus', () => { | ||
| // Initially visible | ||
| expect(codeSearchHint.style.display).toBe(''); | ||
|
|
||
| // Focus the input | ||
| codeSearchInput.focus(); | ||
| codeSearchInput.dispatchEvent(new Event('focus')); | ||
|
|
||
| // Should be hidden | ||
| expect(codeSearchHint.style.display).toBe('none'); | ||
|
|
||
| // Blur the input | ||
| codeSearchInput.blur(); | ||
| codeSearchInput.dispatchEvent(new Event('blur')); | ||
|
|
||
| // Should be visible again | ||
| expect(codeSearchHint.style.display).toBe(''); | ||
| }); | ||
|
|
||
| test('Change event also updates hint visibility', () => { | ||
| // Initially visible | ||
| expect(codeSearchHint.style.display).toBe(''); | ||
|
|
||
| // Set value via change event (e.g., browser autofill) | ||
| codeSearchInput.value = 'autofilled'; | ||
| codeSearchInput.dispatchEvent(new Event('change')); | ||
|
|
||
| // Should be hidden | ||
| expect(codeSearchHint.style.display).toBe('none'); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.