feat: pressing / or s focuses search input (#1855)

This commit is contained in:
Nolan Lawson 2020-08-31 16:06:31 -07:00 committed by GitHub
parent 430ab4db4c
commit 07f23c5990
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 4 deletions

View file

@ -0,0 +1,14 @@
import { store } from '../_store/store'
import { goto } from '../../../__sapper__/client'
import { emit } from '../_utils/eventBus'
// Go to the search page, and also focus the search input. For accessibility
// and usability reasons, this only happens on pressing these particular hotkeys.
export async function goToSearch () {
if (store.get().currentPage === 'search') {
emit('focusSearchInput')
} else {
store.set({ focusSearchInput: true })
goto('/search')
}
}

View file

@ -5,7 +5,7 @@
<Shortcut key="g n" on:pressed="goto('/notifications')"/> <Shortcut key="g n" on:pressed="goto('/notifications')"/>
<Shortcut key="g c" on:pressed="goto('/community')"/> <Shortcut key="g c" on:pressed="goto('/community')"/>
<Shortcut key="g d" on:pressed="goto('/direct')"/> <Shortcut key="g d" on:pressed="goto('/direct')"/>
<Shortcut key="s|/" on:pressed="goto('/search')"/> <Shortcut key="s|/" on:pressed="goToSearch()"/>
<Shortcut key="h|?" on:pressed="showShortcutHelpDialog()"/> <Shortcut key="h|?" on:pressed="showShortcutHelpDialog()"/>
<Shortcut key="c|7" on:pressed="showComposeDialog()"/> <Shortcut key="c|7" on:pressed="showComposeDialog()"/>
{#if !$leftRightChangesFocus} {#if !$leftRightChangesFocus}
@ -23,6 +23,7 @@
import { importShowComposeDialog } from './dialog/asyncDialogs/importShowComposeDialog' import { importShowComposeDialog } from './dialog/asyncDialogs/importShowComposeDialog'
import { store } from '../_store/store' import { store } from '../_store/store'
import { normalizePageName } from '../_utils/normalizePageName' import { normalizePageName } from '../_utils/normalizePageName'
import { goToSearch } from '../_actions/goToSearch'
export default { export default {
store: () => store, store: () => store,
@ -34,6 +35,7 @@
console.log('nav shortcuts') console.log('nav shortcuts')
}, },
goto, goto,
goToSearch,
async showShortcutHelpDialog () { async showShortcutHelpDialog () {
const showShortcutHelpDialog = await importShowShortcutHelpDialog() const showShortcutHelpDialog = await importShowShortcutHelpDialog()
showShortcutHelpDialog() showShortcutHelpDialog()

View file

@ -1,6 +1,7 @@
<form class="search-input-form" on:submit="onSubmit(event)"> <form class="search-input-form" on:submit="onSubmit(event)">
<div class="search-input-wrapper"> <div class="search-input-wrapper">
<input type="search" <input id="the-search-input"
type="search"
class="search-input" class="search-input"
placeholder="Search" placeholder="Search"
aria-label="Search input" aria-label="Search input"
@ -61,8 +62,17 @@
import { doSearch } from '../../_actions/search' import { doSearch } from '../../_actions/search'
import SearchResults from './SearchResults.html' import SearchResults from './SearchResults.html'
import SvgIcon from '../SvgIcon.html' import SvgIcon from '../SvgIcon.html'
import { on } from '../../_utils/eventBus'
import { tryToFocusElement } from '../../_utils/tryToFocusElement'
export default { export default {
oncreate () {
on('focusSearchInput', this, () => this.focusSearchInput()) // user typed hotkey on this page itself
if (this.store.get().focusSearchInput) { // we arrived here from a goto via the search hotkey
this.store.set({ focusSearchInput: false }) // reset
this.focusSearchInput()
}
},
store: () => store, store: () => store,
components: { components: {
LoadingPage, LoadingPage,
@ -70,9 +80,13 @@
SvgIcon SvgIcon
}, },
methods: { methods: {
async onSubmit (e) { onSubmit (e) {
e.preventDefault() e.preventDefault()
doSearch() e.stopPropagation()
/* no await */ doSearch()
},
focusSearchInput () {
tryToFocusElement('the-search-input')
} }
} }
} }

View file

@ -0,0 +1,48 @@
import {
getActiveElementTagName,
getNthStatus,
getUrl,
searchButton, searchInput, searchNavButton
} from '../utils'
import { loginAsFoobar } from '../roles'
import { Selector as $ } from 'testcafe'
fixture`040-shortcuts-search.js`
.page`http://localhost:4002`
test('Pressing / goes to search and focuses input but does not prevent left/right hotkeys afterwards', async t => {
await loginAsFoobar(t)
await t
.expect(getNthStatus(1).exists).ok()
.pressKey('/')
.expect(getUrl()).contains('/search')
.expect(getActiveElementTagName()).match(/input/i)
.typeText(searchInput, 'foo', { paste: true })
.click(searchButton) // unfocus from the input
.expect(getActiveElementTagName()).notMatch(/input/i)
.pressKey('right')
.expect(getUrl()).contains('/settings')
.pressKey('left')
.expect(getUrl()).contains('/search')
// search input is not autofocused if we didn't arrive via the search hotkeys
.expect(getActiveElementTagName()).notMatch(/input/i)
})
test('Pressing / focuses the search input if we are already on the search page', async t => {
await loginAsFoobar(t)
await t
.click(searchNavButton)
.expect(getUrl()).contains('/search')
.expect(getActiveElementTagName()).notMatch(/input/i)
.pressKey('/')
.expect(getActiveElementTagName()).match(/input/i)
})
test('Pressing / without logging in just goes to the search page', async t => {
await t
.expect(getUrl()).eql('http://localhost:4002/')
.expect($('.main-content h1').innerText).eql('Pinafore')
.pressKey('/')
.expect(getUrl()).contains('/search')
.expect(getActiveElementTagName()).notMatch(/input/i)
})

View file

@ -33,6 +33,7 @@ export const logInToInstanceLink = $('a[href="/settings/instances/add"]')
export const copyPasteModeButton = $('.copy-paste-mode-button') export const copyPasteModeButton = $('.copy-paste-mode-button')
export const oauthCodeInput = $('#oauthCodeInput') export const oauthCodeInput = $('#oauthCodeInput')
export const searchInput = $('.search-input') export const searchInput = $('.search-input')
export const searchButton = $('button[aria-label=Search]')
export const postStatusButton = $('.compose-box-button') export const postStatusButton = $('.compose-box-button')
export const showMoreButton = $('.more-items-header button') export const showMoreButton = $('.more-items-header button')
export const accountProfileName = $('.account-profile .account-profile-name') export const accountProfileName = $('.account-profile .account-profile-name')