Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instant search: add product result component #13774

Closed
wants to merge 55 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a4e056f
Instant Search: Implement minimal search results and spelling correct…
gibrown Aug 30, 2019
eedd282
Instant Search: Add filtering display (#13371)
gibrown Sep 4, 2019
05e57bf
Instant Search: Fix search result display bugs and make improvements …
gibrown Sep 9, 2019
9208e5a
Add rudimentary support for filtering on post types (#13430)
jsnmoon Sep 9, 2019
25c4981
Add support for filtering on categories and tags (#13505)
jsnmoon Sep 23, 2019
b94666a
Instant Search: Add instant search sorting based on the URL (#13377)
gibrown Sep 25, 2019
b2c7bc4
Add support for filtering on dates (#13545)
jsnmoon Sep 26, 2019
38ab1d9
Instant Search: Add custom taxonomy filtering (#13605)
jsnmoon Oct 2, 2019
90716e6
Instant Search: add sort widget (#13614)
bluefuton Oct 3, 2019
bbc501e
Instant Search: fix many theme incompatibilities (#13602)
gibrown Oct 8, 2019
670243f
Instant Search: Add infinite scrolling (#13684)
jsnmoon Oct 10, 2019
614c0ab
Instant Search: Add caching to the api requests (#13714)
gibrown Oct 10, 2019
55325ab
Instant Search: Clean up some design bugs/issues (#13721)
gibrown Oct 12, 2019
39aae7f
Jetpack Instant Search: Fix month/year filtering
jsnmoon Oct 14, 2019
67e9a01
Instant Search: Fix labels for post types when we have them. (#13750)
gibrown Oct 14, 2019
e8df94c
Instant Search: Add localization and formatting of all dates (#13748)
gibrown Oct 14, 2019
1d461d7
Instant Search: search from any page on the site (#13713)
gibrown Oct 15, 2019
d21855a
Instant Search: Hook up default options (inc. sort) (#13742)
gibrown Oct 15, 2019
05bf313
Instant Search: Add TrainTracks analytics (#13730)
jsnmoon Oct 15, 2019
964eb57
Merge branch 'master' of github.com:Automattic/jetpack into instant-s…
jsnmoon Oct 15, 2019
d5779ec
Instant Search: Undo auto-format changes
jsnmoon Oct 15, 2019
5ec5b7f
Instant Search: Bump development max bundle size
jsnmoon Oct 15, 2019
1cfbb96
Instant Search: Address Code Climate feedback
jsnmoon Oct 15, 2019
4af0b21
Instant Search: Check for element before children
jsnmoon Oct 16, 2019
2685ccf
Instant Search: Add text domain for _n calls
jsnmoon Oct 16, 2019
50b8b89
Instant Search: Prune unused Gridicons
jsnmoon Oct 16, 2019
573ba32
Instant Search: Remove object-hash usage
jsnmoon Oct 16, 2019
2328d4d
Instant Search: Refactor duplicated filtering code
jsnmoon Oct 16, 2019
1106c0c
Instant Search: Bump max bundle size to 60 KiB
jsnmoon Oct 16, 2019
7f2f6df
Instant Search: Improve mounting approach
jsnmoon Oct 17, 2019
f761606
Instant Search: Restore some Gridicons
jsnmoon Oct 18, 2019
8a147be
Instant Search: Fix search results bug
jsnmoon Oct 18, 2019
414d7ee
Instant Search: Directly use this.props
jsnmoon Oct 18, 2019
2e02827
Instant Search: Improve query handling
jsnmoon Oct 18, 2019
d42657c
Instant Search: Use Jetpack Constants
jsnmoon Oct 18, 2019
163803e
Improve query handling via site_url
jsnmoon Oct 21, 2019
71202d4
Use unique ID prefix for filter controls
jsnmoon Oct 21, 2019
b73e2fb
Use query string as source of truth
jsnmoon Oct 21, 2019
30ecd91
Simplify logic for showing/hiding results
jsnmoon Oct 21, 2019
b6aed9a
Account for query string when getting default sort
jsnmoon Oct 21, 2019
32aee0b
Use query string as source of truth, part 2
jsnmoon Oct 21, 2019
38b02b3
Force descending date sort if no search query
jsnmoon Oct 21, 2019
94e0d87
Create PostTypeIcon component (#13790)
bluefuton Oct 21, 2019
b5715d0
Instant Search: Upgrade to Preact 10 (#13794)
jsnmoon Oct 22, 2019
eea29f5
Force Gridicons to be inline
jsnmoon Oct 22, 2019
d01b1e9
Improve class names for result styling
jsnmoon Oct 22, 2019
2c20003
Bump asset size to 70KiB
jsnmoon Oct 22, 2019
9073238
Instant Search: Force tracking to be ephemeral
jsnmoon Oct 17, 2019
e5c6d21
Instant Search: add comments component (#13797)
bluefuton Oct 22, 2019
9bd1a2e
Add product search result component
bluefuton Oct 18, 2019
4d78559
Refactor renderResult into component
bluefuton Oct 18, 2019
3b767ab
Convert SearchResult to stateless functional component
bluefuton Oct 18, 2019
aab2370
Move comments to new shared component
bluefuton Oct 20, 2019
82277c5
Move Tracks out to common SearchResult
bluefuton Oct 23, 2019
e723c46
Request additional fields for product results
bluefuton Oct 23, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions modules/search/class.jetpack-search.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Constants;

/**
* The main class for the Jetpack Search module.
Expand Down Expand Up @@ -207,19 +208,52 @@ public function init_hooks() {
}

/**
* Loads assets for Jetpack Search Prototype featuring Search As You Type experience.
* Loads assets for Jetpack Instant Search Prototype featuring Search As You Type experience.
*/
public function load_assets() {
if ( defined( 'JETPACK_SEARCH_PROTOTYPE' ) ) {
if ( Constants::is_true( 'JETPACK_SEARCH_PROTOTYPE' ) ) {
$script_relative_path = '_inc/build/instant-search/jp-search.bundle.js';
if ( file_exists( JETPACK__PLUGIN_DIR . $script_relative_path ) ) {
$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' );
$this->load_and_initialize_tracks();

$widget_options = Jetpack_Search_Helpers::get_widgets_from_option();
if ( is_array( $widget_options ) ) {
$widget_options = end( $widget_options );
}

$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;
}

$post_type_objs = get_post_types( array(), 'objects' );
$post_type_labels = array();
foreach ( $post_type_objs as $key => $obj ) {
$post_type_labels[ $key ] = array(
'singular_name' => $obj->labels->singular_name,
'name' => $obj->labels->name,
);
}
// This is probably a temporary filter for testing the prototype.
$options = array(
'siteId' => $_blog_id,
'enableLoadOnScroll' => false,
'locale' => str_replace( '_', '-', get_locale() ),
'postTypeFilters' => $widget_options['post_types'],
'postTypes' => $post_type_labels,
'siteId' => Jetpack::get_option( 'id' ),
'siteUrl' => site_url(),
'sort' => $widget_options['sort'],
'widgets' => array_values( $widgets ),
);
/**
* Customize Instant Search Options.
Expand All @@ -234,7 +268,7 @@ public function load_assets() {

wp_localize_script(
'jetpack-instant-search',
'jetpack_instant_search_options',
'JetpackInstantSearchOptions',
$options
);
}
Expand All @@ -248,6 +282,13 @@ public function load_assets() {
}
}

/**
* Loads scripts for Tracks analytics library
*/
public function load_and_initialize_tracks() {
wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
}

/**
* Get the version number to use when loading the file. Allows us to bypass cache when developing.
*
Expand Down
28 changes: 0 additions & 28 deletions modules/search/instant-search/components/api.js

This file was deleted.

147 changes: 147 additions & 0 deletions modules/search/instant-search/components/gridicon/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/** @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 { __ } from '@wordpress/i18n';

import './style.scss';

class Gridicon extends Component {
needsOffset( icon, size ) {
const iconNeedsOffset = [ 'gridicons-calendar', 'gridicons-cart', 'gridicons-folder' ];

if ( iconNeedsOffset.indexOf( icon ) >= 0 ) {
return size % 18 === 0;
}
return false;
}

getSVGTitle( icon ) {
switch ( icon ) {
default:
return null;
case 'gridicons-audio':
return <title>{ __( 'Has audio' ) }</title>;
case 'gridicons-calendar':
return <title>{ __( 'Is an event' ) }</title>;
case 'gridicons-cart':
return <title>{ __( 'Is a product' ) }</title>;
case 'gridicons-comment':
return <title>{ __( 'Matching comment' ) }</title>;
case 'gridicons-folder':
return <title>{ __( 'Category' ) }</title>;
case 'gridicons-image-multiple':
return <title>{ __( 'Has multiple images' ) }</title>;
case 'gridicons-image':
return <title>{ __( 'Has an image' ) }</title>;
case 'gridicons-page':
return <title>{ __( 'Page' ) }</title>;
case 'gridicons-jetpack-search':
case 'gridicons-search':
return <title>{ __( 'Search' ) }</title>;
case 'gridicons-tag':
return <title>{ __( 'Tag' ) }</title>;
case 'gridicons-video':
return <title>{ __( 'Has a video' ) }</title>;
}
}

renderIcon( icon ) {
switch ( icon ) {
default:
return null;
case 'gridicons-audio':
return (
<g>
<path d="M8 4v10.184C7.686 14.072 7.353 14 7 14c-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3V7h7v4.184c-.314-.112-.647-.184-1-.184-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3V4H8z" />
</g>
);
case 'gridicons-calendar':
return (
<g>
<path d="M19 4h-1V2h-2v2H8V2H6v2H5c-1.105 0-2 .896-2 2v13c0 1.104.895 2 2 2h14c1.104 0 2-.896 2-2V6c0-1.104-.896-2-2-2zm0 15H5V8h14v11z" />
</g>
);
case 'gridicons-cart':
return (
<g>
<path d="M9 20c0 1.1-.9 2-2 2s-1.99-.9-1.99-2S5.9 18 7 18s2 .9 2 2zm8-2c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2zm.396-5c.937 0 1.75-.65 1.952-1.566L21 5H7V4c0-1.105-.895-2-2-2H3v2h2v11c0 1.105.895 2 2 2h12c0-1.105-.895-2-2-2H7v-2h10.396z" />
</g>
);
case 'gridicons-comment':
return (
<g>
<path d="M3 6v9c0 1.105.895 2 2 2h9v5l5.325-3.804c1.05-.75 1.675-1.963 1.675-3.254V6c0-1.105-.895-2-2-2H5c-1.105 0-2 .895-2 2z" />
</g>
);
case 'gridicons-folder':
return (
<g>
<path d="M18 19H6c-1.1 0-2-.9-2-2V7c0-1.1.9-2 2-2h3c1.1 0 2 .9 2 2h7c1.1 0 2 .9 2 2v8c0 1.1-.9 2-2 2z" />
</g>
);
case 'gridicons-image-multiple':
return (
<g>
<path d="M15 7.5c0-.828.672-1.5 1.5-1.5s1.5.672 1.5 1.5S17.328 9 16.5 9 15 8.328 15 7.5zM4 20h14c0 1.105-.895 2-2 2H4c-1.1 0-2-.9-2-2V8c0-1.105.895-2 2-2v14zM22 4v12c0 1.105-.895 2-2 2H8c-1.105 0-2-.895-2-2V4c0-1.105.895-2 2-2h12c1.105 0 2 .895 2 2zM8 4v6.333L11 7l4.855 5.395.656-.73c.796-.886 2.183-.886 2.977 0l.513.57V4H8z" />
</g>
);
case 'gridicons-pages':
return (
<g>
<path d="M16 8H8V6h8v2zm0 2H8v2h8v-2zm4-6v12l-6 6H6c-1.105 0-2-.895-2-2V4c0-1.105.895-2 2-2h12c1.105 0 2 .895 2 2zm-2 10V4H6v16h6v-4c0-1.105.895-2 2-2h4z" />
</g>
);
case 'gridicons-tag':
return (
<g>
<path d="M20 2.007h-7.087c-.53 0-1.04.21-1.414.586L2.592 11.5c-.78.78-.78 2.046 0 2.827l7.086 7.086c.78.78 2.046.78 2.827 0l8.906-8.906c.376-.374.587-.883.587-1.413V4.007c0-1.105-.895-2-2-2zM17.007 9c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2z" />
</g>
);
case 'gridicons-video':
return (
<g>
<path d="M20 4v2h-2V4H6v2H4V4c-1.105 0-2 .895-2 2v12c0 1.105.895 2 2 2v-2h2v2h12v-2h2v2c1.105 0 2-.895 2-2V6c0-1.105-.895-2-2-2zM6 16H4v-3h2v3zm0-5H4V8h2v3zm4 4V9l4.5 3-4.5 3zm10 1h-2v-3h2v3zm0-5h-2V8h2v3z" />
</g>
);
}
}

render() {
const { size = 24, class_name = '' } = this.props;
const icon = 'gridicons-' + this.props.icon,
needsOffset = this.needsOffset( icon, size );

let iconClass = [ 'gridicon', icon, class_name ];

if ( needsOffset ) {
iconClass.push( 'needs-offset' );
}
iconClass = iconClass.join( ' ' );

return (
<svg
className={ iconClass }
height={ size }
width={ size }
onClick={ this.props.onClick }
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
{ this.getSVGTitle( icon ) }
{ this.renderIcon( icon ) }
</svg>
);
}
}

export default Gridicon;
16 changes: 16 additions & 0 deletions modules/search/instant-search/components/gridicon/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.gridicon {
fill: currentColor;
display: inline-block;

&.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 */
}
}
69 changes: 69 additions & 0 deletions modules/search/instant-search/components/post-type-icon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/** @jsx h */

/**
* External dependencies
*/
import { h } from 'preact';

/**
* Internal dependencies
*/
import Gridicon from './gridicon';
import arrayOverlap from '../lib/array-overlap';

const KNOWN_SHORTCODE_TYPES = {
video: [
'youtube',
'ooyala',
'anvplayer',
'wpvideo',
'bc_video',
'video',
'brightcove',
'tp_video',
'jwplayer',
'tempo-video',
'vimeo',
],
gallery: [ 'gallery', 'ione_media_gallery' ],
audio: [ 'audio', 'soundcloud' ],
};

const POST_TYPE_TO_ICON_MAP = {
product: 'cart',
video: 'video',
gallery: 'image-multiple',
event: 'calendar',
events: 'calendar',
};

const PostTypeIcon = ( { postType, shortcodeTypes, imageCount, iconSize = 18 } ) => {
// Do we have a special icon for this post type?
if ( Object.keys( POST_TYPE_TO_ICON_MAP ).includes( postType ) ) {
return <Gridicon icon={ POST_TYPE_TO_ICON_MAP[ postType ] } size={ iconSize } />;
}

// Otherwise, choose the icon based on whether the post has certain shortcodes
const hasVideo = arrayOverlap( shortcodeTypes, KNOWN_SHORTCODE_TYPES.video );
const hasAudio = arrayOverlap( shortcodeTypes, KNOWN_SHORTCODE_TYPES.audio );
const hasGallery = arrayOverlap( shortcodeTypes, KNOWN_SHORTCODE_TYPES.gallery );

if ( hasVideo ) {
return <Gridicon icon="video" size={ iconSize } />;
} else if ( hasAudio ) {
return <Gridicon icon="audio" size={ iconSize } />;
}

switch ( postType ) {
case 'page':
return <Gridicon icon="pages" size={ iconSize } />;
default:
if ( hasGallery || imageCount > 1 ) {
return <Gridicon icon="image-multiple" size={ iconSize } />;
}
}

return null;
};

export default PostTypeIcon;
47 changes: 47 additions & 0 deletions modules/search/instant-search/components/scroll-button.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/** @jsx h */

/**
* External dependencies
*/
import { h, Component } from 'preact';
import { __ } from '@wordpress/i18n';
// 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 debounce from 'lodash/debounce';

class ScrollButton extends Component {
componentDidMount() {
this.props.enableLoadOnScroll && document.addEventListener( 'scroll', this.checkScroll );
}
componentDidUnmount() {
document.removeEventListener( 'scroll', this.checkScroll );
}

checkScroll = debounce( () => {
if (
this.props.enableLoadOnScroll &&
window.innerHeight + window.scrollY === document.body.offsetHeight
) {
this.props.onLoadNextPage();
}
}, 100 );

render() {
return (
<button
className="jetpack-instant-search__scroll-button"
disabled={ this.props.isLoading }
onClick={ this.props.onLoadNextPage }
>
{ this.props.isLoading ? (
<span>{ __( 'Loading…', 'jetpack' ) }</span>
) : (
<span>{ __( 'Load more', 'jetpack' ) }</span>
) }
</button>
);
}
}

export default ScrollButton;
Loading