diff --git a/modules/search/class.jetpack-search.php b/modules/search/class.jetpack-search.php
index dc87b16a53996..396bd4a62d32d 100644
--- a/modules/search/class.jetpack-search.php
+++ b/modules/search/class.jetpack-search.php
@@ -204,10 +204,24 @@ public function load_assets() {
$script_version = self::get_asset_version( $script_relative_path );
$script_path = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
wp_enqueue_script( 'jetpack-instant-search', $script_path, array(), $script_version, true );
- $_blog_id = Jetpack::get_option( 'id' );
+
+ $filters = Jetpack_Search_Helpers::get_filters_from_widgets();
+ $widgets = array();
+ foreach( $filters as $key => $filter ) {
+ if ( ! isset( $widgets[$filter[ 'widget_id' ]] ) ) {
+ $widgets[$filter[ 'widget_id' ]][ 'filters' ] = array();
+ $widgets[$filter[ 'widget_id' ]][ 'widget_id' ] = $filter[ 'widget_id' ];
+ }
+ $new_filter = $filter;
+ $new_filter[ 'filter_id' ] = $key;
+ $widgets[$filter[ 'widget_id' ]][ 'filters' ][] = $new_filter;
+ }
+
// This is probably a temporary filter for testing the prototype.
$options = array(
- 'siteId' => $_blog_id,
+ 'postTypes' => get_post_types(),
+ 'siteId' => Jetpack::get_option( 'id' ),
+ 'widgets' => array_values( $widgets ),
);
/**
* Customize Instant Search Options.
@@ -221,9 +235,7 @@ public function load_assets() {
$options = apply_filters( 'jetpack_instant_search_options', $options );
wp_localize_script(
- 'jetpack-instant-search',
- 'jetpack_instant_search_options',
- $options
+ 'jetpack-instant-search', 'JetpackInstantSearchOptions', $options
);
}
diff --git a/modules/search/instant-search/components/api.js b/modules/search/instant-search/components/api.js
deleted file mode 100644
index 0cbbbcd5cefb1..0000000000000
--- a/modules/search/instant-search/components/api.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * External dependencies
- */
-import fetch from 'unfetch';
-
-const FIELDS = [
- 'author',
- 'comment_count',
- 'date',
- 'excerpt_html',
- 'gravatar_url',
- 'permalink.url.raw',
- 'title_html',
-];
-
-function stringifyArray( fieldName, array ) {
- return array.map( ( element, index ) => `${ fieldName }[${ index }]=${ element }` ).join( '&' );
-}
-
-function getAPIUrl( siteId, query ) {
- return `https://public-api.wordpress.com/rest/v1.3/sites/${ siteId }/search?query=${ encodeURIComponent(
- query
- ) }&${ stringifyArray( 'fields', FIELDS ) }`;
-}
-
-export function search( siteId, query ) {
- return fetch( getAPIUrl( siteId, query ) );
-}
diff --git a/modules/search/instant-search/components/gridicon/index.jsx b/modules/search/instant-search/components/gridicon/index.jsx
new file mode 100644
index 0000000000000..a0419136ffe58
--- /dev/null
+++ b/modules/search/instant-search/components/gridicon/index.jsx
@@ -0,0 +1,380 @@
+/** @jsx h **/
+
+/* !!!
+This is a fork of the Jetpack Gridicon code:
+ https://github.com/Automattic/jetpack/blob/f8078c2cd12ac508334da2fb08e37a92cf283c14/_inc/client/components/gridicon/index.jsx
+
+It has been modified to work with Preact, and only includes the icons that we need.
+!!! */
+
+/**
+ * External dependencies
+ */
+import { h, Component } from 'preact';
+import './style.scss';
+
+class Gridicon extends Component {
+ needsOffset( icon, size ) {
+ const iconNeedsOffset = [
+ 'gridicons-add-outline',
+ 'gridicons-add',
+ 'gridicons-align-image-center',
+ 'gridicons-align-image-left',
+ 'gridicons-align-image-none',
+ 'gridicons-align-image-right',
+ 'gridicons-attachment',
+ 'gridicons-backspace',
+ 'gridicons-bold',
+ 'gridicons-bookmark-outline',
+ 'gridicons-bookmark',
+ 'gridicons-calendar',
+ 'gridicons-cart',
+ 'gridicons-create',
+ 'gridicons-custom-post-type',
+ 'gridicons-external',
+ 'gridicons-folder',
+ 'gridicons-heading',
+ 'gridicons-help-outline',
+ 'gridicons-help',
+ 'gridicons-history',
+ 'gridicons-info-outline',
+ 'gridicons-info',
+ 'gridicons-italic',
+ 'gridicons-layout-blocks',
+ 'gridicons-link-break',
+ 'gridicons-link',
+ 'gridicons-list-checkmark',
+ 'gridicons-list-ordered',
+ 'gridicons-list-unordered',
+ 'gridicons-menus',
+ 'gridicons-minus',
+ 'gridicons-my-sites',
+ 'gridicons-notice-outline',
+ 'gridicons-notice',
+ 'gridicons-plans',
+ 'gridicons-plus-small',
+ 'gridicons-plus',
+ 'gridicons-popout',
+ 'gridicons-posts',
+ 'gridicons-scheduled',
+ 'gridicons-share-ios',
+ 'gridicons-star-outline',
+ 'gridicons-star',
+ 'gridicons-stats',
+ 'gridicons-status',
+ 'gridicons-thumbs-up',
+ 'gridicons-textcolor',
+ 'gridicons-time',
+ 'gridicons-trophy',
+ 'gridicons-user-circle',
+ ];
+
+ if ( iconNeedsOffset.indexOf( icon ) >= 0 ) {
+ return size % 18 === 0;
+ }
+ return false;
+ }
+
+ needsOffsetX( icon, size ) {
+ const iconNeedsOffsetX = [
+ 'gridicons-arrow-down',
+ 'gridicons-arrow-up',
+ 'gridicons-comment',
+ 'gridicons-clear-formatting',
+ 'gridicons-flag',
+ 'gridicons-menu',
+ 'gridicons-reader',
+ 'gridicons-strikethrough',
+ ];
+
+ if ( iconNeedsOffsetX.indexOf( icon ) >= 0 ) {
+ return size % 18 === 0;
+ }
+ return false;
+ }
+
+ needsOffsetY( icon, size ) {
+ const iconNeedsOffsetY = [
+ 'gridicons-align-center',
+ 'gridicons-align-justify',
+ 'gridicons-align-left',
+ 'gridicons-align-right',
+ 'gridicons-arrow-left',
+ 'gridicons-arrow-right',
+ 'gridicons-house',
+ 'gridicons-indent-left',
+ 'gridicons-indent-right',
+ 'gridicons-minus-small',
+ 'gridicons-print',
+ 'gridicons-sign-out',
+ 'gridicons-stats-alt',
+ 'gridicons-trash',
+ 'gridicons-underline',
+ 'gridicons-video-camera',
+ ];
+
+ if ( iconNeedsOffsetY.indexOf( icon ) >= 0 ) {
+ return size % 18 === 0;
+ }
+ return false;
+ }
+
+ renderIcon( icon ) {
+ switch ( icon ) {
+ default:
+ return null;
+ case 'gridicons-attachment':
+ return (
+
+
+
+ );
+ case 'gridicons-audio':
+ return (
+
+
+
+ );
+ case 'gridicons-calendar':
+ return (
+
+
+
+ );
+ case 'gridicons-camera':
+ return (
+
+
+
+ );
+ case 'gridicons-cart':
+ return (
+
+
+
+ );
+ case 'gridicons-chevron-down':
+ return (
+
+
+
+ );
+ case 'gridicons-chevron-left':
+ return (
+
+
+
+ );
+ case 'gridicons-chevron-right':
+ return (
+
+
+
+ );
+ case 'gridicons-chevron-up':
+ return (
+
+
+
+ );
+ case 'gridicons-code':
+ return (
+
+
+
+ );
+ case 'gridicons-comment':
+ return (
+
+
+
+ );
+ case 'gridicons-cross-small':
+ return (
+
+
+
+ );
+ case 'gridicons-cross':
+ return (
+
+
+
+ );
+ case 'gridicons-dropdown':
+ return (
+
+
+
+ );
+ case 'gridicons-external':
+ return (
+
+
+
+ );
+ case 'gridicons-folder-multiple':
+ return (
+
+
+
+ );
+ case 'gridicons-folder':
+ return (
+
+
+
+ );
+ case 'gridicons-image-multiple':
+ return (
+
+
+
+ );
+ case 'gridicons-image':
+ return (
+
+
+
+ );
+ case 'gridicons-location':
+ return (
+
+
+
+ );
+ case 'gridicons-mention':
+ return (
+
+
+
+ );
+ case 'gridicons-my-sites':
+ return (
+
+
+
+ );
+ case 'gridicons-pages':
+ return (
+
+
+
+ );
+ case 'gridicons-plus-small':
+ return (
+
+
+
+ );
+ case 'gridicons-plus':
+ return (
+
+
+
+ );
+ case 'gridicons-reader':
+ return (
+
+
+
+ );
+ case 'gridicons-refresh':
+ return (
+
+
+
+ );
+ case 'gridicons-search':
+ return (
+
+
+
+ );
+ case 'gridicons-star-outline':
+ return (
+
+
+
+ );
+ case 'gridicons-star':
+ return (
+
+
+
+ );
+ case 'gridicons-stats-alt':
+ return (
+
+
+
+ );
+ case 'gridicons-stats':
+ return (
+
+
+
+ );
+ case 'gridicons-tag':
+ return (
+
+
+
+ );
+ case 'gridicons-user-circle':
+ return (
+
+
+
+ );
+ case 'gridicons-user':
+ return (
+
+
+
+ );
+ case 'gridicons-video':
+ return (
+
+
+
+ );
+ }
+ }
+
+ render() {
+ const { size = 24, class_name = '' } = this.props;
+ const icon = 'gridicons-' + this.props.icon,
+ needsOffset = this.needsOffset( icon, size ),
+ needsOffsetX = this.needsOffsetX( icon, size ),
+ needsOffsetY = this.needsOffsetY( icon, size );
+
+ let iconClass = [ 'gridicon', icon, class_name ];
+
+ if ( needsOffset ) {
+ iconClass.push( 'needs-offset' );
+ }
+ if ( needsOffsetX ) {
+ iconClass.push( 'needs-offset-x' );
+ }
+ if ( needsOffsetY ) {
+ iconClass.push( 'needs-offset-y' );
+ }
+ iconClass = iconClass.join( ' ' );
+
+ return (
+
+ );
+ }
+}
+
+export default Gridicon;
diff --git a/modules/search/instant-search/components/gridicon/style.scss b/modules/search/instant-search/components/gridicon/style.scss
new file mode 100644
index 0000000000000..196bcc1a30875
--- /dev/null
+++ b/modules/search/instant-search/components/gridicon/style.scss
@@ -0,0 +1,15 @@
+.gridicon {
+ fill: currentColor;
+
+ &.needs-offset g {
+ transform: translate( 1px, 1px ); /* translates to .5px because it's in a child element */
+ }
+
+ &.needs-offset-x g {
+ transform: translate( 1px, 0 ); /* only nudges horizontally */
+ }
+
+ &.needs-offset-y g {
+ transform: translate( 0, 1px ); /* only nudges vertically */
+ }
+}
diff --git a/modules/search/instant-search/components/search-filter-dates.jsx b/modules/search/instant-search/components/search-filter-dates.jsx
new file mode 100644
index 0000000000000..2d8e3340fcaba
--- /dev/null
+++ b/modules/search/instant-search/components/search-filter-dates.jsx
@@ -0,0 +1,42 @@
+/** @jsx h */
+
+/**
+ * External dependencies
+ */
+import { h, Component } from 'preact';
+import strip from 'strip';
+
+export default class SearchFilterDates extends Component {
+ render() {
+ return (
+
+
{ this.props.filter.name }
+
+
+ );
+ }
+}
diff --git a/modules/search/instant-search/components/search-filter-post-types.jsx b/modules/search/instant-search/components/search-filter-post-types.jsx
new file mode 100644
index 0000000000000..3ff2751426221
--- /dev/null
+++ b/modules/search/instant-search/components/search-filter-post-types.jsx
@@ -0,0 +1,38 @@
+/** @jsx h */
+
+/**
+ * External dependencies
+ */
+import { h, Component } from 'preact';
+import strip from 'strip';
+
+export default class SearchFilterPostTypes extends Component {
+ renderPostType = ( { key, doc_count: count } ) => {
+ const name = this.props.postTypes[ key ];
+ return (
+
+
+
+
+ );
+ };
+ render() {
+ return (
+
+
{ this.props.filter.name }
+
+ { this.props.aggregation &&
+ 'buckets' in this.props.aggregation &&
+ this.props.aggregation.buckets.map( this.renderPostType ) }
+
+
+ );
+ }
+}
diff --git a/modules/search/instant-search/components/search-filter-taxonomies.jsx b/modules/search/instant-search/components/search-filter-taxonomies.jsx
new file mode 100644
index 0000000000000..fbd93a7fcfdba
--- /dev/null
+++ b/modules/search/instant-search/components/search-filter-taxonomies.jsx
@@ -0,0 +1,34 @@
+/** @jsx h */
+
+/**
+ * External dependencies
+ */
+import { h, Component } from 'preact';
+import strip from 'strip';
+
+export default class SearchFilterTaxonomies extends Component {
+ render() {
+ return (
+
+
{ this.props.filter.name }
+
+
+ );
+ }
+}
diff --git a/modules/search/instant-search/components/search-filters-widget.jsx b/modules/search/instant-search/components/search-filters-widget.jsx
new file mode 100644
index 0000000000000..efbefd835cb9a
--- /dev/null
+++ b/modules/search/instant-search/components/search-filters-widget.jsx
@@ -0,0 +1,56 @@
+/** @jsx h */
+
+/**
+ * External dependencies
+ */
+import { h, Component } from 'preact';
+// NOTE: We only import the debounce package here for to reduced bundle size.
+// Do not import the entire lodash library!
+// eslint-disable-next-line lodash/import-scope
+import get from 'lodash/get';
+
+/**
+ * Internal dependencies
+ */
+import SearchFilterDates from './search-filter-dates';
+import SearchFilterTaxonomies from './search-filter-taxonomies';
+import SearchFilterPostTypes from './search-filter-post-types';
+
+export default class SearchFiltersWidget extends Component {
+ renderFilterComponent = ( { filter, results } ) => {
+ switch ( filter.type ) {
+ case 'date_histogram':
+ return results && ;
+ case 'taxonomy':
+ return results && ;
+ case 'post_type':
+ return (
+ results && (
+
+ )
+ );
+ }
+ };
+
+ render() {
+ const aggregations = get( this.props.results, 'aggregations' );
+ return (
+
+ { get( this.props.widget, 'filters' )
+ .map( filter =>
+ aggregations ? { filter, results: aggregations[ filter.filter_id ] } : null
+ )
+ .filter( data => !! data )
+ .filter(
+ ( { results } ) =>
+ !! results && Array.isArray( results.buckets ) && results.buckets.length > 0
+ )
+ .map( this.renderFilterComponent ) }
+
+ );
+ }
+}
diff --git a/modules/search/instant-search/components/search-filters-widget.scss b/modules/search/instant-search/components/search-filters-widget.scss
new file mode 100644
index 0000000000000..d836499e7453a
--- /dev/null
+++ b/modules/search/instant-search/components/search-filters-widget.scss
@@ -0,0 +1,5 @@
+.jetpack-search-filters-widget__filter-list {
+ label {
+ display: inline-block;
+ }
+}
diff --git a/modules/search/instant-search/components/search-result-minimal.jsx b/modules/search/instant-search/components/search-result-minimal.jsx
new file mode 100644
index 0000000000000..f03f54135af28
--- /dev/null
+++ b/modules/search/instant-search/components/search-result-minimal.jsx
@@ -0,0 +1,105 @@
+/** @jsx h */
+
+/**
+ * External dependencies
+ */
+import { h, Component } from 'preact';
+import strip from 'strip';
+
+/**
+ * Internal dependencies
+ */
+import Gridicon from './gridicon';
+
+class SearchResultMinimal extends Component {
+ render() {
+ const { result_type, fields, highlight } = this.props.result;
+ const IconSize = 18;
+ if ( result_type !== 'post' ) {
+ return null;
+ }
+ const url = new URL( 'http://' + fields[ 'permalink.url.raw' ] );
+ const path = url.pathname;
+ const no_content = ! highlight.content || highlight.content[ 0 ] === '';
+
+ let tags = fields[ 'tag.name.default' ];
+ if ( ! tags ) {
+ tags = [];
+ }
+ if ( ! Array.isArray( tags ) ) {
+ tags = [ tags ];
+ }
+
+ let cats = fields[ 'category.name.default' ];
+ if ( ! cats ) {
+ cats = [];
+ }
+ if ( ! Array.isArray( cats ) ) {
+ cats = [ cats ];
+ }
+
+ return (
+
+
+
+
+
+ { strip( fields.date ).split( ' ' )[ 0 ] }
+
+
+ { no_content && (
+
+
{ path }
+
+ { tags.map( tag => (
+
+
+ { tag }
+
+ ) ) }
+
+
+ { cats.map( cat => (
+
+
+ { cat }
+
+ ) ) }
+
+
+ ) }
+ { ! no_content && (
+
+ ) }
+
+ { highlight.comments && (
+
+
+
+
+ ) }
+
+ );
+ }
+}
+
+export default SearchResultMinimal;
diff --git a/modules/search/instant-search/components/search-result-minimal.scss b/modules/search/instant-search/components/search-result-minimal.scss
new file mode 100644
index 0000000000000..0fb9d8e935beb
--- /dev/null
+++ b/modules/search/instant-search/components/search-result-minimal.scss
@@ -0,0 +1,39 @@
+.jetpack-instant-search__result-minimal {
+ padding: 0.125em 0;
+ margin: 1em 0;
+ position: relative;
+}
+.jetpack-instant-search__result-minimal .gridicon {
+ margin-right: 5px;
+}
+
+.jetpack-instant-search__result-minimal h3 {
+ overflow: hidden;
+}
+
+.jetpack-instant-search__result-minimal-date {
+ margin: 0.5em 0;
+ display: flex;
+ float: right;
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ font-size: 0.85em;
+}
+
+.jetpack-instant-search__result-minimal-tag span,
+.jetpack-instant-search__result-minimal-cat span {
+ margin-right: 0.5em;
+}
+
+.jetpack-instant-search__result-minimal-tag,
+.jetpack-instant-search__result-minimal-cat,
+.jetpack-instant-search__result-minimal-comment {
+ padding-left: 10px;
+}
+
+.jetpack-instant-search__result-minimal-title b,
+.jetpack-instant-search__result-minimal-content b,
+.jetpack-instant-search__result-minimal-comment-span b {
+ text-decoration: underline;
+}
diff --git a/modules/search/instant-search/components/search-result.jsx b/modules/search/instant-search/components/search-result.jsx
deleted file mode 100644
index 26df807f00ce9..0000000000000
--- a/modules/search/instant-search/components/search-result.jsx
+++ /dev/null
@@ -1,42 +0,0 @@
-/** @jsx h */
-
-/**
- * External dependencies
- */
-import { sprintf, _n } from '@wordpress/i18n';
-import { h, Component } from 'preact';
-import strip from 'strip';
-
-class SearchResult extends Component {
- render() {
- return (
-
-
- { strip( this.props.result.fields.title_html ) || 'Unknown Title' }
- {' '}
-
- { strip( this.props.result.fields.author ) }{' '}
-
- { strip( this.props.result.fields.date ).split( ' ' )[ 0 ] }
-
-
-
- { strip( this.props.result.fields.excerpt_html ) }
-
-
- { sprintf(
- _n( '%d comment', '%d comments', this.props.result.fields.comment_count, 'jetpack' ),
- this.props.result.fields.comment_count
- ) }
-
-
- );
- }
-}
-
-export default SearchResult;
diff --git a/modules/search/instant-search/components/search-result.scss b/modules/search/instant-search/components/search-result.scss
deleted file mode 100644
index 5bb1b9926d472..0000000000000
--- a/modules/search/instant-search/components/search-result.scss
+++ /dev/null
@@ -1,28 +0,0 @@
-@import '../../../../_inc/client/scss/variables/colors';
-
-.jetpack-instant-search__result {
- padding: 0.125em 0;
- margin: 1em 0;
- a {
- box-shadow: inset 0 0 0 rgba( black, 0 ), 0 1px 0 rgba( black, 1 );
- transition: color 80ms ease-in, box-shadow 130ms ease-in-out;
- }
- a:hover,
- a:focus {
- box-shadow: inset 0 0 0 rgba( black, 0 ), 0 3px 0 rgba( black, 1 );
- }
-}
-
-.jetpack-instant-search__result-author-and-date {
- margin: 0.5em 0;
- display: flex;
- justify-content: space-between;
-}
-
-.jetpack-instant-search__result-date {
- color: $gray;
-}
-
-.jetpack-instant-search__result-excerpt {
- color: $gray;
-}
diff --git a/modules/search/instant-search/components/search-results.jsx b/modules/search/instant-search/components/search-results.jsx
index 6c631c27444b1..55340d2086d29 100644
--- a/modules/search/instant-search/components/search-results.jsx
+++ b/modules/search/instant-search/components/search-results.jsx
@@ -9,17 +9,50 @@ import { h, Component } from 'preact';
/**
* Internal dependencies
*/
-import SearchResult from './search-result';
+import SearchResultMinimal from './search-result-minimal';
class SearchResults extends Component {
+ render_result( result ) {
+ switch ( this.props.resultFormat ) {
+ case 'engagement':
+ case 'product':
+ case 'minimal':
+ default:
+ return ;
+ }
+ }
+
render() {
- const { results = [], query } = this.props;
+ const { results = [], query, total = 0, corrected_query = false } = this.props;
+ if ( query === '' ) {
+ return ;
+ }
+ if ( total === 0 ) {
+ return (
+
+
+
{ sprintf( __( 'No Results.' ), query ) }
+
+
+ );
+ }
+
return (
-
{ sprintf( __( 'You are searching for: "%s"' ), query ) }
- { results.map( result => (
-
- ) ) }
+
+ { corrected_query !== false
+ ? sprintf( __( 'Showing results for "%s"' ), corrected_query )
+ : sprintf( __( 'Results for "%s"' ), query ) }
+
+ { corrected_query !== false && (
+
+ { sprintf( __( 'No results for "%s"' ), query ) }
+
+ ) }
+
+ { sprintf( __( '%d Results' ), total ) }
+
+ { 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
new file mode 100644
index 0000000000000..0a093ae4fac6d
--- /dev/null
+++ b/modules/search/instant-search/components/search-results.scss
@@ -0,0 +1,23 @@
+.jetpack-instant-search__search-results {
+ padding: 0.125em 0;
+ margin: 1em 0;
+ position: relative;
+}
+
+.jetpack-instant-search__search-results-count {
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+
+.jetpack-instant-search__search-results-real-query {
+ font-size: 1.5em;
+ text-decoration: bold;
+}
+
+.jetpack-instant-search__search-results-unused-query,
+.jetpack-instant-search__search-results-real-query {
+ overflow: hidden;
+ margin: 0;
+ padding: 0;
+}
diff --git a/modules/search/instant-search/components/search-widget.jsx b/modules/search/instant-search/components/search-widget.jsx
index 07f6140e9d43d..4fb965c6d6dd0 100644
--- a/modules/search/instant-search/components/search-widget.jsx
+++ b/modules/search/instant-search/components/search-widget.jsx
@@ -3,7 +3,7 @@
/**
* External dependencies
*/
-import { h, Component } from 'preact';
+import Preact, { h, Component } from 'preact';
import Portal from 'preact-portal';
// NOTE: We only import the debounce package here for to reduced bundle size.
// Do not import the entire lodash library!
@@ -14,27 +14,39 @@ import debounce from 'lodash/debounce';
* Internal dependencies
*/
import SearchResults from './search-results';
-import { search } from './api';
+import SearchFiltersWidget from './search-filters-widget';
+import { search, buildFilterAggregations } from '../lib/api';
import { setSearchQuery } from '../lib/query-string';
+import { removeChildren, hideSearchHeader } from '../lib/dom';
class SearchApp extends Component {
constructor() {
super( ...arguments );
+ this.input = Preact.createRef();
this.requestId = 0;
+ this.props.resultFormat = 'minimal';
+ this.props.aggregations = buildFilterAggregations( this.props.options.widgets );
+ this.props.widgets = this.props.options.widgets ? this.props.options.widgets : [];
this.state = {
query: this.props.initialValue,
- results: [],
+ results: {},
};
this.getResults = debounce( this.getResults, 500 );
this.getResults( this.props.initialValue );
}
+
componentDidMount() {
if ( this.props.grabFocus ) {
- this.input.focus();
+ this.input.current.focus();
}
+
+ hideSearchHeader();
+ removeChildren( document.querySelector( 'main' ) );
+ this.props.widgets.forEach( function( widget ) {
+ removeChildren( document.getElementById( widget.widget_id ) );
+ } );
}
- bindInput = input => ( this.input = input );
onChangeQuery = event => {
const query = event.target.value;
this.setState( { query } );
@@ -47,7 +59,13 @@ class SearchApp extends Component {
this.requestId++;
const requestId = this.requestId;
- search( this.props.siteId, query )
+ search(
+ this.props.options.siteId,
+ query,
+ this.props.aggregations,
+ {},
+ this.props.options.resultFormat
+ )
.then( response => response.json() )
.then( json => {
if ( this.requestId === requestId ) {
@@ -62,19 +80,44 @@ class SearchApp extends Component {
render() {
const { query, results } = this.state;
return (
-
-
-
-
+
+ { this.props.widgets.map( ( widget, index ) => (
+
+
+
+ { /* TODO: Add support for preserving label text */ }
+
+
+
+
+
+
+
+ ) ) }
+
-
+
-
+
);
}
}
diff --git a/modules/search/instant-search/index.jsx b/modules/search/instant-search/index.jsx
index 65b8274f3be66..7a12766b734b6 100644
--- a/modules/search/instant-search/index.jsx
+++ b/modules/search/instant-search/index.jsx
@@ -11,37 +11,19 @@ import { h, render } from 'preact';
import SearchWidget from './components/search-widget';
import { getSearchQuery } from './lib/query-string';
-function removeChildren( htmlElement ) {
- while ( htmlElement.lastChild ) {
- htmlElement.removeChild( htmlElement.lastChild );
- }
-}
-
-const hideSearchHeader = () => {
- const titleElements = document.getElementById( 'content' ).getElementsByClassName( 'page-title' );
- if ( titleElements.length > 0 ) {
- titleElements[ 0 ].style.display = 'none';
- }
-};
-
-const injectSearchWidget = ( initialValue, target, siteId, grabFocus ) => {
+const injectSearchWidget = ( initialValue, grabFocus ) => {
render(
- ,
- target
+ ,
+ document.body
);
};
document.addEventListener( 'DOMContentLoaded', function() {
- //This var is provided by wp_localize_script() so we have limited control
- const options = jetpack_instant_search_options; // eslint-disable-line no-undef
-
- if ( 'siteId' in options && document.body && document.body.classList.contains( 'search' ) ) {
- const widget = document.querySelector( '.widget_search' );
- if ( !! widget ) {
- removeChildren( widget );
- removeChildren( document.querySelector( 'main' ) );
- hideSearchHeader();
- injectSearchWidget( getSearchQuery(), widget, options.siteId );
- }
+ if ( !! window.JetpackInstantSearchOptions && 'siteId' in window.JetpackInstantSearchOptions ) {
+ injectSearchWidget( getSearchQuery() );
}
} );
diff --git a/modules/search/instant-search/instant-search.scss b/modules/search/instant-search/instant-search.scss
index c42b08a53d8a0..8bbacfb1c1b23 100644
--- a/modules/search/instant-search/instant-search.scss
+++ b/modules/search/instant-search/instant-search.scss
@@ -1 +1,5 @@
-@import './components/search-result.scss';
+@import './components/search-results.scss';
+@import './components/search-filters-widget.scss';
+@import './components/search-result-minimal.scss';
+//@import './components/search-result-engagement.scss';
+//@import './components/search-result-product.scss';
diff --git a/modules/search/instant-search/lib/api.js b/modules/search/instant-search/lib/api.js
new file mode 100644
index 0000000000000..0cef521b28608
--- /dev/null
+++ b/modules/search/instant-search/lib/api.js
@@ -0,0 +1,65 @@
+/**
+ * External dependencies
+ */
+import fetch from 'unfetch';
+import { encode } from 'qss';
+import { flatten } from 'q-flat';
+
+export function buildFilterAggregations( widgets = [] ) {
+ const aggregation = {};
+ widgets.forEach( ( { filters: widgetFilters } ) =>
+ widgetFilters.forEach( filter => {
+ switch ( filter.type ) {
+ case 'date_histogram': {
+ const field = filter.field === 'post_date_gmt' ? 'date_gmt' : 'date';
+ aggregation[ filter.filter_id ] = {
+ date_histogram: { field, interval: filter.interval },
+ };
+ break;
+ }
+ case 'taxonomy': {
+ let field = `taxonomy.${ filter.taxonomy }.slug`;
+ if ( filter.taxonomy === 'post_tag' ) {
+ field = 'tag.slug';
+ } else if ( filter.type === 'category' ) {
+ field = 'category.slug';
+ }
+ aggregation[ filter.filter_id ] = { terms: { field, size: filter.count } };
+ break;
+ }
+ case 'post_type': {
+ aggregation[ filter.filter_id ] = { terms: { field: filter.type, size: filter.count } };
+ break;
+ }
+ }
+ } )
+ );
+ return aggregation;
+}
+
+export function search( siteId, query, aggregations, filter, resultFormat ) {
+ let fields = [];
+ let highlight_fields = [];
+ switch ( resultFormat ) {
+ case 'engagement':
+ case 'product':
+ case 'minimal':
+ default:
+ highlight_fields = [ 'title', 'content', 'comments' ];
+ fields = [ 'date', 'permalink.url.raw', 'tag.name.default', 'category.name.default' ];
+ }
+
+ const queryString = encode(
+ flatten( {
+ aggregations,
+ fields,
+ highlight_fields,
+ filter,
+ query: encodeURIComponent( query ),
+ } )
+ );
+
+ return fetch(
+ `https://public-api.wordpress.com/rest/v1.3/sites/${ siteId }/search?${ queryString }`
+ );
+}
diff --git a/modules/search/instant-search/lib/dom.js b/modules/search/instant-search/lib/dom.js
new file mode 100644
index 0000000000000..707f88d241c56
--- /dev/null
+++ b/modules/search/instant-search/lib/dom.js
@@ -0,0 +1,12 @@
+export function removeChildren( htmlElement ) {
+ while ( htmlElement.lastChild ) {
+ htmlElement.removeChild( htmlElement.lastChild );
+ }
+}
+
+export function hideSearchHeader() {
+ const title = document.querySelector( '#content .page-title' );
+ if ( title ) {
+ title.style.display = 'none';
+ }
+}
diff --git a/package.json b/package.json
index 13af00d7bdd7f..425d035c7bff0 100644
--- a/package.json
+++ b/package.json
@@ -132,6 +132,7 @@
"preact": "8.4.2",
"preact-portal": "1.1.3",
"prop-types": "15.7.2",
+ "q-flat": "1.0.7",
"qss": "2.0.3",
"react-pure-render": "1.0.2",
"react-redux": "6.0.1",
diff --git a/webpack.config.js b/webpack.config.js
index 3fac55f661935..7bf09ce9f594f 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -85,8 +85,8 @@ module.exports = [
performance: isDevelopment
? {}
: {
- maxAssetSize: 30000,
- maxEntrypointSize: 30000,
+ maxAssetSize: 35000,
+ maxEntrypointSize: 35000,
hints: 'error',
},
},
diff --git a/yarn.lock b/yarn.lock
index 5c0387a172241..b2dc5ebcdb569 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10307,6 +10307,11 @@ puppeteer@1.19.0:
rimraf "^2.6.1"
ws "^6.1.0"
+q-flat@1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/q-flat/-/q-flat-1.0.7.tgz#4cf81aede99d2a9acbafcf503af0d0184a850d3d"
+ integrity sha512-Ug+B6yajVE5HF7eAszOvAcYmQ+DbYaDcQlxYuW9RaAqwZTRZQq+lHMGqHlnaxKP7CfuGCpXQXOb4qymRYMkYEQ==
+
q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"