let requestTree = (query, baseComponent, dispatch) => { | let requestTree = (query, baseComponent, dispatch) => { | ||||
dispatch(startFetching()); | dispatch(startFetching()); | ||||
return getTree(baseComponent.key, { q: query, s: 'qualifier,name' }) | |||||
const params = { s: 'qualifier,name', qualifiers: 'BRC,FIL,UTS' }; | |||||
if (query) { | |||||
params.q = query; | |||||
} | |||||
return getTree(baseComponent.key, params) | |||||
.then(r => dispatch(searchAction(r.components))) | .then(r => dispatch(searchAction(r.components))) | ||||
.then(() => dispatch(stopFetching())); | .then(() => dispatch(stopFetching())); | ||||
}; | }; | ||||
export function search (query, baseComponent) { | export function search (query, baseComponent) { | ||||
return dispatch => { | return dispatch => { | ||||
dispatch(updateQueryAction(query)); | dispatch(updateQueryAction(query)); | ||||
if (query) { | |||||
if (query != null) { | |||||
requestTree(query, baseComponent, dispatch); | requestTree(query, baseComponent, dispatch); | ||||
} else { | } else { | ||||
dispatch(searchAction(null)); | dispatch(searchAction(null)); |
import Breadcrumbs from './Breadcrumbs'; | import Breadcrumbs from './Breadcrumbs'; | ||||
import SourceViewer from './SourceViewer'; | import SourceViewer from './SourceViewer'; | ||||
import Search from './Search'; | import Search from './Search'; | ||||
import { initComponent, browse } from '../actions'; | |||||
import { initComponent, browse, search } from '../actions'; | |||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
componentDidMount () { | componentDidMount () { | ||||
const { dispatch, component, routing } = this.props; | const { dispatch, component, routing } = this.props; | ||||
const selectedKey = (routing.path && decodeURIComponent(routing.path.substr(1))) || component.key; | const selectedKey = (routing.path && decodeURIComponent(routing.path.substr(1))) || component.key; | ||||
dispatch(initComponent(component.key, component.breadcrumbs)) | dispatch(initComponent(component.key, component.breadcrumbs)) | ||||
.then(() => dispatch(browse(selectedKey))); | .then(() => dispatch(browse(selectedKey))); | ||||
this.handleKeyDown = this.handleKeyDown.bind(this); | |||||
this.attachShortcuts(); | |||||
} | } | ||||
componentWillReceiveProps (nextProps) { | componentWillReceiveProps (nextProps) { | ||||
} | } | ||||
} | } | ||||
componentWillUnmount () { | |||||
this.removeShortcuts(); | |||||
} | |||||
attachShortcuts () { | |||||
window.addEventListener('keyup', this.handleKeyDown); | |||||
} | |||||
removeShortcuts () { | |||||
window.removeEventListener('keyup', this.handleKeyDown); | |||||
} | |||||
handleBrowse (component) { | handleBrowse (component) { | ||||
const { dispatch } = this.props; | const { dispatch } = this.props; | ||||
dispatch(browse(component.key)); | dispatch(browse(component.key)); | ||||
} | } | ||||
handleKeyDown (e) { | |||||
const { dispatch, component, searchQuery } = this.props; | |||||
// "t" key | |||||
if (e.keyCode === 84 && searchQuery == null) { | |||||
dispatch(search('', component)); | |||||
} | |||||
} | |||||
render () { | render () { | ||||
const { | const { | ||||
fetching, | fetching, | ||||
const shouldShowSearchResults = !!searchResults; | const shouldShowSearchResults = !!searchResults; | ||||
const shouldShowSourceViewer = !!sourceViewer; | const shouldShowSourceViewer = !!sourceViewer; | ||||
const shouldShowComponents = !shouldShowSearchResults && !shouldShowSourceViewer && components; | const shouldShowComponents = !shouldShowSearchResults && !shouldShowSourceViewer && components; | ||||
const shouldShowBreadcrumbs = !shouldShowSearchResults && Array.isArray(breadcrumbs) && breadcrumbs.length > 1; | |||||
const shouldShowBreadcrumbs = !shouldShowSearchResults && Array.isArray(breadcrumbs) && breadcrumbs.length > 1; | |||||
const componentsClassName = classNames('spacer-top', { 'new-loading': fetching }); | const componentsClassName = classNames('spacer-top', { 'new-loading': fetching }); | ||||
<i className="spinner"/> | <i className="spinner"/> | ||||
</div> | </div> | ||||
<Search component={this.props.component}/> | |||||
<div className="page-actions"> | |||||
<Search component={this.props.component}/> | |||||
</div> | |||||
</header> | </header> | ||||
{errorMessage && ( | {errorMessage && ( |
import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||
import { search } from '../actions'; | import { search } from '../actions'; | ||||
import { translate } from '../../../helpers/l10n'; | |||||
class Search extends Component { | class Search extends Component { | ||||
componentDidMount () { | componentDidMount () { | ||||
this.refs.input.focus(); | |||||
this.focusSearchInput(); | |||||
} | |||||
componentDidUpdate () { | |||||
this.focusSearchInput(); | |||||
} | |||||
focusSearchInput () { | |||||
if (this.refs.input) { | |||||
this.refs.input.focus(); | |||||
} | |||||
} | } | ||||
handleSearch (e) { | handleSearch (e) { | ||||
e.preventDefault(); | e.preventDefault(); | ||||
const { dispatch, component } = this.props; | const { dispatch, component } = this.props; | ||||
const query = this.refs.input.value; | |||||
const query = this.refs.input ? this.refs.input.value : ''; | |||||
dispatch(search(query, component)); | dispatch(search(query, component)); | ||||
} | } | ||||
handleStopSearch (e) { | |||||
e.preventDefault(); | |||||
const { dispatch } = this.props; | |||||
dispatch(search(null)); | |||||
} | |||||
handleKeyDown (e) { | |||||
const { dispatch } = this.props; | |||||
// "escape" key | |||||
if (e.keyCode === 27) { | |||||
dispatch(search(null)); | |||||
} | |||||
} | |||||
render () { | render () { | ||||
const { query } = this.props; | const { query } = this.props; | ||||
const hasQuery = query != null; | |||||
return ( | return ( | ||||
<form | <form | ||||
onSubmit={this.handleSearch.bind(this)} | onSubmit={this.handleSearch.bind(this)} | ||||
className="search-box code-search-box"> | className="search-box code-search-box"> | ||||
<button className="search-box-submit button-clean"> | |||||
<i className="icon-search"></i> | |||||
</button> | |||||
<input | |||||
ref="input" | |||||
onChange={this.handleSearch.bind(this)} | |||||
value={query} | |||||
className="search-box-input" | |||||
type="search" | |||||
name="q" | |||||
placeholder="Search" | |||||
maxLength="100" | |||||
autoComplete="off"/> | |||||
{hasQuery && ( | |||||
<input | |||||
ref="input" | |||||
onChange={this.handleSearch.bind(this)} | |||||
onKeyDown={this.handleKeyDown.bind(this)} | |||||
value={query} | |||||
className="search-box-input" | |||||
type="search" | |||||
name="q" | |||||
placeholder="Search" | |||||
maxLength="100" | |||||
autoComplete="off" | |||||
style={{ visibility: hasQuery ? 'visible': 'hidden' }}/> | |||||
)} | |||||
{!hasQuery && ( | |||||
<button className="search-box-submit"> | |||||
{translate('search_verb')} | |||||
</button> | |||||
)} | |||||
{hasQuery && ( | |||||
<button | |||||
className="search-box-submit" | |||||
onClick={this.handleStopSearch.bind(this)}> | |||||
{translate('cancel')} | |||||
</button> | |||||
)} | |||||
</form> | </form> | ||||
); | ); | ||||
} | } |
breadcrumbs: null, | breadcrumbs: null, | ||||
sourceViewer: null, | sourceViewer: null, | ||||
searchResults: null, | searchResults: null, | ||||
searchQuery: '', | |||||
searchQuery: null, | |||||
coverageMetric: null, | coverageMetric: null, | ||||
baseBreadcrumbs: [], | baseBreadcrumbs: [], | ||||
errorMessage: null | errorMessage: null | ||||
breadcrumbs, | breadcrumbs, | ||||
sourceViewer, | sourceViewer, | ||||
searchResults: null, | searchResults: null, | ||||
searchQuery: '', | |||||
searchQuery: null, | |||||
errorMessage: null | errorMessage: null | ||||
}; | }; | ||||
case SEARCH: | case SEARCH: |
</ul> | </ul> | ||||
</div> | </div> | ||||
<h3 class="shortcuts-section-title">{{t 'shortcuts.section.rules'}}</h3> | |||||
<div class="spacer-bottom"> | |||||
<h3 class="shortcuts-section-title">{{t 'shortcuts.section.rules'}}</h3> | |||||
<ul class="shortcuts-list"> | |||||
<li><span class="shortcut-button">↑</span> <span | |||||
class="shortcut-button">↓</span> {{t 'shortcuts.section.rules.navigate_between_rules'}}</li> | |||||
<li><span class="shortcut-button">→</span> {{t 'shortcuts.section.rules.open_details'}}</li> | |||||
<li><span class="shortcut-button">←</span> {{t 'shortcuts.section.rules.return_to_list'}}</li> | |||||
<li><span class="shortcut-button">a</span> {{t 'shortcuts.section.rules.activate'}}</li> | |||||
<li><span class="shortcut-button">d</span> {{t 'shortcuts.section.rules.deactivate'}}</li> | |||||
</ul> | |||||
</div> | |||||
<h3 class="shortcuts-section-title">{{t 'shortcuts.section.code'}}</h3> | |||||
<ul class="shortcuts-list"> | <ul class="shortcuts-list"> | ||||
<li><span class="shortcut-button">↑</span> <span | |||||
class="shortcut-button">↓</span> {{t 'shortcuts.section.rules.navigate_between_rules'}}</li> | |||||
<li><span class="shortcut-button">→</span> {{t 'shortcuts.section.rules.open_details'}}</li> | |||||
<li><span class="shortcut-button">←</span> {{t 'shortcuts.section.rules.return_to_list'}}</li> | |||||
<li><span class="shortcut-button">a</span> {{t 'shortcuts.section.rules.activate'}}</li> | |||||
<li><span class="shortcut-button">d</span> {{t 'shortcuts.section.rules.deactivate'}}</li> | |||||
<li><span class="shortcut-button">t</span> {{t 'shortcuts.section.code.search'}}</li> | |||||
</ul> | </ul> | ||||
</div> | </div> | ||||
it('should be reset', () => { | it('should be reset', () => { | ||||
const stateBefore = Object.assign({}, initialState, { searchQuery: 'query' }); | const stateBefore = Object.assign({}, initialState, { searchQuery: 'query' }); | ||||
expect(current(stateBefore, browseAction(exampleComponent)).searchQuery) | expect(current(stateBefore, browseAction(exampleComponent)).searchQuery) | ||||
.to.equal(''); | |||||
.to.be.null; | |||||
}); | }); | ||||
}); | }); | ||||
describe('errorMessage', () => { | describe('errorMessage', () => { |