diff --git a/modules/search/instant-search/components/gridicon/index.jsx b/modules/search/instant-search/components/gridicon/index.jsx index 6064300862dfd..795f048324181 100644 --- a/modules/search/instant-search/components/gridicon/index.jsx +++ b/modules/search/instant-search/components/gridicon/index.jsx @@ -143,6 +143,7 @@ class Gridicon extends Component { return { __( 'Has multiple images' ) }; case 'gridicons-image': return { __( 'Has an image' ) }; + case 'gridicons-jetpack-search': case 'gridicons-search': return { __( 'Search' ) }; case 'gridicons-tag': @@ -324,6 +325,12 @@ class Gridicon extends Component { ); + case 'gridicons-jetpack-search': + return ( + + + + ); case 'gridicons-star-outline': return ( diff --git a/modules/search/instant-search/components/search-widget.jsx b/modules/search/instant-search/components/search-app.jsx similarity index 71% rename from modules/search/instant-search/components/search-widget.jsx rename to modules/search/instant-search/components/search-app.jsx index 7c591fd0bc8f1..0d741dc8419fd 100644 --- a/modules/search/instant-search/components/search-widget.jsx +++ b/modules/search/instant-search/components/search-app.jsx @@ -16,6 +16,7 @@ import debounce from 'lodash/debounce'; import SearchResults from './search-results'; import SearchFiltersWidget from './search-filters-widget'; import SearchSortWidget from './search-sort-widget'; +import SearchBox from './search-box'; import { search, buildFilterAggregations } from '../lib/api'; import { setSearchQuery, @@ -24,7 +25,7 @@ import { setSortQuery, getSortQuery, } from '../lib/query-string'; -import { removeChildren, hideSearchHeader } from '../lib/dom'; +import { removeChildren, hideElements } from '../lib/dom'; class SearchApp extends Component { constructor() { @@ -38,8 +39,9 @@ class SearchApp extends Component { query: this.props.initialValue, sort: this.props.initialSort, results: {}, + loading: false, }; - this.getResults = debounce( this.getResults, 500 ); + this.getResults = debounce( this.getResults, 200 ); this.getResults( this.state.query, getFilterQuery(), this.state.sort ); } @@ -48,11 +50,15 @@ class SearchApp extends Component { this.input.current.focus(); } - hideSearchHeader(); - removeChildren( document.querySelector( 'main' ) ); + hideElements( this.props.themeOptions.elem_selectors ); + removeChildren( document.querySelector( this.props.themeOptions.results_selector ) ); this.props.widgets.forEach( function( widget ) { removeChildren( document.getElementById( widget.widget_id ) ); } ); + const searchForms = document.querySelectorAll( this.props.themeOptions.search_form_selector ); + searchForms.forEach( function( elem ) { + removeChildren( elem ); + } ); } onChangeQuery = event => { @@ -77,6 +83,9 @@ class SearchApp extends Component { this.requestId++; const requestId = this.requestId; + this.setState( { + loading: true, + } ); search( { aggregations: this.props.aggregations, filter, @@ -86,36 +95,36 @@ class SearchApp extends Component { sort, } ).then( results => { if ( this.requestId === requestId ) { - this.setState( { results } ); + this.setState( { + results, + loading: false, + } ); } } ); } else { - this.setState( { results: [] } ); + this.setState( { + results: [], + loading: false, + } ); } }; render() { const { query, results } = this.state; + const searchForms = Array.from( + document.querySelectorAll( this.props.themeOptions.search_form_selector ) + ); return ( - { this.props.widgets.map( ( widget, index ) => ( + { this.props.widgets.map( widget => (
- { /* TODO: Add support for preserving label text */ } - -
) ) } - + { searchForms && + searchForms.map( elem => ( + + + + ) ) } + + diff --git a/modules/search/instant-search/components/search-box.jsx b/modules/search/instant-search/components/search-box.jsx new file mode 100644 index 0000000000000..02d1bdeb03bed --- /dev/null +++ b/modules/search/instant-search/components/search-box.jsx @@ -0,0 +1,30 @@ +/** @jsx h */ + +/** + * External dependencies + */ +import { h, Component } from 'preact'; +import { __ } from '@wordpress/i18n'; + +class SearchBox extends Component { + render() { + const { query, onChangeQuery, appRef } = this.props; + + return ( +
+ { /* TODO: Add support for preserving label text */ } + { __( 'Search', 'jetpack' ) } + +
+ ); + } +} + +export default SearchBox; diff --git a/modules/search/instant-search/components/search-box.scss b/modules/search/instant-search/components/search-box.scss new file mode 100644 index 0000000000000..a88c88ad01086 --- /dev/null +++ b/modules/search/instant-search/components/search-box.scss @@ -0,0 +1,8 @@ +/* apply to all the inputs to try and pick up any theme styling */ +.jp-instant-search__box input { + border-radius: 2px; + font-size: 14px; + height: 26px; + width: 100%; + line-height: 1.2em; +} diff --git a/modules/search/instant-search/components/search-filters-widget.jsx b/modules/search/instant-search/components/search-filters-widget.jsx index 16d417fefa640..412f1deab1e5f 100644 --- a/modules/search/instant-search/components/search-filters-widget.jsx +++ b/modules/search/instant-search/components/search-filters-widget.jsx @@ -60,8 +60,13 @@ export default class SearchFiltersWidget extends Component { render() { const aggregations = get( this.props.results, 'aggregations' ); + const cls = + this.props.loading === true + ? 'jetpack-instant-search__filters-widget jetpack-instant-search__is-loading' + : 'jetpack-instant-search__filters-widget'; + return ( -
+
{ get( this.props.widget, 'filters' ) .map( configuration => aggregations diff --git a/modules/search/instant-search/components/search-results.jsx b/modules/search/instant-search/components/search-results.jsx index 61f57c077a792..8756e2d3241a2 100644 --- a/modules/search/instant-search/components/search-results.jsx +++ b/modules/search/instant-search/components/search-results.jsx @@ -3,7 +3,7 @@ /** * External dependencies */ -import { __, sprintf } from '@wordpress/i18n'; +import { __, _n, sprintf } from '@wordpress/i18n'; import { h, Component } from 'preact'; /** @@ -23,7 +23,7 @@ class SearchResults extends Component { } render() { - const { results = [], query, total = 0, corrected_query = false } = this.props; + const { results = [], query, total = 0, corrected_query = false, loading = false } = this.props; if ( query === '' ) { return
; } @@ -31,25 +31,31 @@ class SearchResults extends Component { return (
-

{ sprintf( __( 'No Results.' ), query ) }

+

{ sprintf( __( 'No Results.', 'jetpack' ), query ) }

); } + const num = new Intl.NumberFormat().format( total ); + const cls = + loading === true + ? 'jetpack-instant-search__search-results jetpack-instant-search__is-loading' + : 'jetpack-instant-search__search-results'; return ( -
- - { sprintf( __( '%d Results' ), total ) } - +

{ corrected_query !== false - ? sprintf( __( 'Showing results for "%s"' ), corrected_query ) - : sprintf( __( 'Results for "%s"' ), query ) } + ? sprintf( + _n( 'Showing %s result for "%s"', 'Showing %s results for "%s"', total ), + num, + corrected_query + ) + : sprintf( _n( '%s results for "%s"', '%s results for "%s"', total ), num, query ) }

{ corrected_query !== false && (

- { sprintf( __( 'No results for "%s"' ), query ) } + { sprintf( __( 'No results for "%s"', 'jetpack' ), query ) }

) } { results.map( result => this.render_result( result ) ) } diff --git a/modules/search/instant-search/components/search-results.scss b/modules/search/instant-search/components/search-results.scss index 1a1b621e8d4fe..c2a4a60c29323 100644 --- a/modules/search/instant-search/components/search-results.scss +++ b/modules/search/instant-search/components/search-results.scss @@ -1,13 +1,8 @@ .jetpack-instant-search__search-results { - padding: 0.125em 0; - margin: 1em 0; + padding: 0.125em 2em; + margin: 1em auto; position: relative; -} - -.jetpack-instant-search__search-results-count { - float: right; - margin-left: 0.5em; - display: block; + max-width: 1080px; } .jetpack-instant-search__search-results-real-query { diff --git a/modules/search/instant-search/index.jsx b/modules/search/instant-search/index.jsx index 501d9b782be3a..2e825b410d3bd 100644 --- a/modules/search/instant-search/index.jsx +++ b/modules/search/instant-search/index.jsx @@ -8,18 +8,20 @@ import { h, render } from 'preact'; /** * Internal dependencies */ -import SearchWidget from './components/search-widget'; +import SearchApp from './components/search-app'; import { getSearchQuery, getFilterQuery, getSortQuery } from './lib/query-string'; +import { getThemeOptions } from './lib/dom'; import { SERVER_OBJECT_NAME } from './lib/constants'; -const injectSearchWidget = grabFocus => { +const injectSearchApp = grabFocus => { render( - , document.body ); @@ -31,6 +33,6 @@ document.addEventListener( 'DOMContentLoaded', function() { 'siteId' in window[ SERVER_OBJECT_NAME ] && document.body.classList.contains( 'search' ) ) { - injectSearchWidget(); + injectSearchApp(); } } ); diff --git a/modules/search/instant-search/instant-search.scss b/modules/search/instant-search/instant-search.scss index 081299e0fda13..811e29a8996a3 100644 --- a/modules/search/instant-search/instant-search.scss +++ b/modules/search/instant-search/instant-search.scss @@ -2,5 +2,10 @@ @import './components/search-filters-widget.scss'; @import './components/search-sort-widget.scss'; @import './components/search-result-minimal.scss'; +@import './components/search-box.scss'; //@import './components/search-result-engagement.scss'; //@import './components/search-result-product.scss'; + +.jetpack-instant-search__is-loading { + opacity: 0.2; +} diff --git a/modules/search/instant-search/lib/dom.js b/modules/search/instant-search/lib/dom.js index 5008236a771df..ac63fc5681e3e 100644 --- a/modules/search/instant-search/lib/dom.js +++ b/modules/search/instant-search/lib/dom.js @@ -4,10 +4,13 @@ export function removeChildren( htmlElement ) { } } -export function hideSearchHeader() { - const title = document.querySelector( '#content .page-title' ); - if ( title ) { - title.style.display = 'none'; +export function hideElements( elem_selectors ) { + let elem = null; + for ( let i = 0; i < elem_selectors.length; i++ ) { + elem = document.querySelector( elem_selectors[ i ] ); + if ( elem ) { + elem.style.display = 'none'; + } } } @@ -16,3 +19,51 @@ export function getCheckedInputNames( parentDom ) { .filter( input => input.checked ) .map( input => input.name ); } + +export function getThemeOptions( searchOptions ) { + //the order here matters + const result_selectors = [ + //2015, 2016, 2017, 2019, argent, astra, storefront + 'main', + //2010, 2011, 2012, 2013, 2014 + '#content', + //colormag, shapely, sydney, zerif lite + '#primary', + //hemingway + '.content', + ]; + + const potential_removals = [ + '#content .page-title', + 'section.ast-archive-description', + //'input.search-submit', ??? + ]; + + let options = { + results_selector: null, + elem_selectors: [], + search_form_selector: 'form#searchform, form.search-form, form.searchform', + }; + + //sample the dom to try and find a location to mount results + for ( let i = 0; i < result_selectors.length; i++ ) { + if ( document.querySelector( result_selectors[ i ] ) ) { + options.results_selector = result_selectors[ i ]; + break; + } + } + + //look for elements we should remove + for ( let i = 0; i < potential_removals.length; i++ ) { + if ( document.querySelector( potential_removals[ i ] ) ) { + options.elem_selectors.push( potential_removals[ i ] ); + } + } + + if ( searchOptions.theme_options ) { + //apply overrides from user filters + options = { ...options, ...searchOptions.theme_options }; + } + + return options; +}