Skip to content

Commit

Permalink
Instant Search: Update prototype with downstream changes (#13761)
Browse files Browse the repository at this point in the history
* Implement minimal search results and spelling correction (#13365)
* Add filtering display (#13371)
* Fix search result display bugs and make improvements (#13393)
* Add rudimentary support for filtering on post types (#13430)
* Add support for filtering on categories and tags (#13505)
* Add instant search sorting based on the URL (#13377)
* Add support for filtering on dates (#13545)
* Add custom taxonomy filtering (#13605)
* add sort widget (#13614)
* fix many theme incompatibilities (#13602)
* Add infinite scrolling (#13684)
* Add caching to the api requests (#13714)
* Clean up some design bugs/issues (#13721)
* Fix labels for post types when we have them. (#13750)
* Add localization and formatting of all dates (#13748)
* search from any page on the site (#13713)
* Hook up default options (inc. sort) (#13742)
* Add TrainTracks analytics (#13730)
* Create PostTypeIcon component (#13790)
* Upgrade to Preact 10 (#13794)
* Add comments component (#13797)
* Address review feedback
  • Loading branch information
jsnmoon authored Oct 23, 2019
1 parent 7ec49bc commit 90605a8
Show file tree
Hide file tree
Showing 35 changed files with 1,774 additions and 272 deletions.
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

0 comments on commit 90605a8

Please sign in to comment.