feat: pressing / or s focuses search input (#1855)
This commit is contained in:
parent
430ab4db4c
commit
07f23c5990
14
src/routes/_actions/goToSearch.js
Normal file
14
src/routes/_actions/goToSearch.js
Normal 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')
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
48
tests/spec/040-shortcuts-search.js
Normal file
48
tests/spec/040-shortcuts-search.js
Normal 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)
|
||||||
|
})
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in a new issue