@@ -148,7 +148,14 @@ function retrieveComponent (componentKey, bucket) { | |||
let requestTree = (query, baseComponent, dispatch) => { | |||
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(() => dispatch(stopFetching())); | |||
}; | |||
@@ -199,7 +206,7 @@ export function browse (componentKey) { | |||
export function search (query, baseComponent) { | |||
return dispatch => { | |||
dispatch(updateQueryAction(query)); | |||
if (query) { | |||
if (query != null) { | |||
requestTree(query, baseComponent, dispatch); | |||
} else { | |||
dispatch(searchAction(null)); |
@@ -25,7 +25,7 @@ import Components from './Components'; | |||
import Breadcrumbs from './Breadcrumbs'; | |||
import SourceViewer from './SourceViewer'; | |||
import Search from './Search'; | |||
import { initComponent, browse } from '../actions'; | |||
import { initComponent, browse, search } from '../actions'; | |||
import { translate } from '../../../helpers/l10n'; | |||
@@ -33,8 +33,12 @@ class Code extends Component { | |||
componentDidMount () { | |||
const { dispatch, component, routing } = this.props; | |||
const selectedKey = (routing.path && decodeURIComponent(routing.path.substr(1))) || component.key; | |||
dispatch(initComponent(component.key, component.breadcrumbs)) | |||
.then(() => dispatch(browse(selectedKey))); | |||
this.handleKeyDown = this.handleKeyDown.bind(this); | |||
this.attachShortcuts(); | |||
} | |||
componentWillReceiveProps (nextProps) { | |||
@@ -47,11 +51,32 @@ class Code extends Component { | |||
} | |||
} | |||
componentWillUnmount () { | |||
this.removeShortcuts(); | |||
} | |||
attachShortcuts () { | |||
window.addEventListener('keyup', this.handleKeyDown); | |||
} | |||
removeShortcuts () { | |||
window.removeEventListener('keyup', this.handleKeyDown); | |||
} | |||
handleBrowse (component) { | |||
const { dispatch } = this.props; | |||
dispatch(browse(component.key)); | |||
} | |||
handleKeyDown (e) { | |||
const { dispatch, component, searchQuery } = this.props; | |||
// "t" key | |||
if (e.keyCode === 84 && searchQuery == null) { | |||
dispatch(search('', component)); | |||
} | |||
} | |||
render () { | |||
const { | |||
fetching, | |||
@@ -65,7 +90,7 @@ class Code extends Component { | |||
const shouldShowSearchResults = !!searchResults; | |||
const shouldShowSourceViewer = !!sourceViewer; | |||
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 }); | |||
@@ -80,7 +105,9 @@ class Code extends Component { | |||
<i className="spinner"/> | |||
</div> | |||
<Search component={this.props.component}/> | |||
<div className="page-actions"> | |||
<Search component={this.props.component}/> | |||
</div> | |||
</header> | |||
{errorMessage && ( |
@@ -21,40 +21,80 @@ import React, { Component } from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { search } from '../actions'; | |||
import { translate } from '../../../helpers/l10n'; | |||
class Search extends Component { | |||
componentDidMount () { | |||
this.refs.input.focus(); | |||
this.focusSearchInput(); | |||
} | |||
componentDidUpdate () { | |||
this.focusSearchInput(); | |||
} | |||
focusSearchInput () { | |||
if (this.refs.input) { | |||
this.refs.input.focus(); | |||
} | |||
} | |||
handleSearch (e) { | |||
e.preventDefault(); | |||
const { dispatch, component } = this.props; | |||
const query = this.refs.input.value; | |||
const query = this.refs.input ? this.refs.input.value : ''; | |||
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 () { | |||
const { query } = this.props; | |||
const hasQuery = query != null; | |||
return ( | |||
<form | |||
onSubmit={this.handleSearch.bind(this)} | |||
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> | |||
); | |||
} |
@@ -76,7 +76,7 @@ export const initialState = { | |||
breadcrumbs: null, | |||
sourceViewer: null, | |||
searchResults: null, | |||
searchQuery: '', | |||
searchQuery: null, | |||
coverageMetric: null, | |||
baseBreadcrumbs: [], | |||
errorMessage: null | |||
@@ -104,7 +104,7 @@ export function current (state = initialState, action) { | |||
breadcrumbs, | |||
sourceViewer, | |||
searchResults: null, | |||
searchQuery: '', | |||
searchQuery: null, | |||
errorMessage: null | |||
}; | |||
case SEARCH: |
@@ -23,14 +23,21 @@ | |||
</ul> | |||
</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"> | |||
<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> | |||
</div> | |||
@@ -225,7 +225,7 @@ describe('Code :: Store', () => { | |||
it('should be reset', () => { | |||
const stateBefore = Object.assign({}, initialState, { searchQuery: 'query' }); | |||
expect(current(stateBefore, browseAction(exampleComponent)).searchQuery) | |||
.to.equal(''); | |||
.to.be.null; | |||
}); | |||
}); | |||
describe('errorMessage', () => { |