diff --git a/projects/packages/search/changelog/add-inline-search-branding b/projects/packages/search/changelog/add-inline-search-branding
new file mode 100644
index 0000000000000..3630b8beef243
--- /dev/null
+++ b/projects/packages/search/changelog/add-inline-search-branding
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Inline Search: add Jetpack Branding to search results.
diff --git a/projects/packages/search/changelog/add-inline-search-term-highlighting b/projects/packages/search/changelog/add-inline-search-term-highlighting
new file mode 100644
index 0000000000000..e8bd7beb03ccb
--- /dev/null
+++ b/projects/packages/search/changelog/add-inline-search-term-highlighting
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Add highlighting of search term in returned search results.
diff --git a/projects/packages/search/src/inline-search/class-inline-search-colophon.php b/projects/packages/search/src/inline-search/class-inline-search-colophon.php
new file mode 100644
index 0000000000000..aedc367069a1b
--- /dev/null
+++ b/projects/packages/search/src/inline-search/class-inline-search-colophon.php
@@ -0,0 +1,155 @@
+is_valid_search_query( $query ) ) {
+ return;
+ }
+
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) );
+ add_action( 'wp_footer', array( $this, 'register_colophon_script' ) );
+ }
+
+ /**
+ * Enqueue theme-specific styles for the search colophon.
+ * This is hooked to wp_enqueue_scripts to ensure styles load properly in the head.
+ *
+ * @since $$next-version$$
+ */
+ public function enqueue_styles() {
+ $handle = 'jetpack-search-inline-colophon';
+ $this->register_component_style( $handle, 'colophon.css' );
+ }
+
+ /**
+ * Register and configure the JavaScript for displaying the colophon.
+ *
+ * @since $$next-version$$
+ */
+ public function register_colophon_script() {
+ $this->register_inline_search_script();
+
+ // Don't localize if already localized to prevent duplication
+ if ( wp_script_is( self::SCRIPT_HANDLE, 'data' ) ) {
+ $localized_data = wp_scripts()->get_data( self::SCRIPT_HANDLE, 'data' );
+ if ( $localized_data && strpos( $localized_data, 'JetpackSearchColophon' ) !== false ) {
+ return;
+ }
+ }
+
+ // Only localize the script, don't register it again as it's handled by the base class
+ wp_localize_script(
+ self::SCRIPT_HANDLE,
+ 'JetpackSearchColophon',
+ array(
+ 'html' => $this->get_colophon_html(),
+ 'selector' => $this->format_selectors_for_query( $this->get_content_selectors() ),
+ )
+ );
+ }
+
+ /**
+ * Get selectors where colophon will be displayed.
+ *
+ * @since $$next-version$$
+ * @return array CSS selectors for content container elements.
+ */
+ private function get_content_selectors() {
+ $default_selectors = array(
+ '.wp-block-query',
+ '.content',
+ '#content',
+ '#site-content',
+ '.site-main',
+ '.content-area',
+ );
+
+ /**
+ * Filter the selectors where colophon appears.
+ *
+ * @since $$next-version$$
+ * @param array $default_selectors CSS selectors for content container elements.
+ */
+ return apply_filters( 'jetpack_search_colophon_selectors', $default_selectors );
+ }
+
+ /**
+ * Get the locale for the Jetpack URL.
+ *
+ * @return string|null The locale prefix or null.
+ */
+ private function get_locale_prefix() {
+ $locale = get_locale();
+ if ( empty( $locale ) ) {
+ return null;
+ }
+
+ $locale_prefix = explode( '-', $locale )[0];
+ return $locale_prefix !== 'en' ? $locale_prefix : null;
+ }
+
+ /**
+ * Generate the HTML for the colophon.
+ *
+ * @return string The HTML for the colophon.
+ */
+ private function get_colophon_html() {
+ $locale_prefix = $this->get_locale_prefix();
+ $url = $locale_prefix
+ ? 'https://' . $locale_prefix . '.jetpack.com/upgrade/search?utm_source=poweredby'
+ : 'https://jetpack.com/upgrade/search/?utm_source=poweredby';
+
+ $logo_svg = $this->get_logo_svg();
+
+ return sprintf(
+ '
',
+ esc_url( $url ),
+ $logo_svg,
+ esc_html__( 'Search powered by Jetpack', 'jetpack-search-pkg' )
+ );
+ }
+
+ /**
+ * Get the SVG markup for the Jetpack logo.
+ *
+ * @return string The SVG markup.
+ */
+ private function get_logo_svg() {
+ $color_jetpack = '#069E08';
+ $color_white = '#ffffff';
+ $logo_size = 12;
+
+ return sprintf(
+ '',
+ $logo_size,
+ $color_jetpack,
+ $color_white
+ );
+ }
+}
diff --git a/projects/packages/search/src/inline-search/class-inline-search-component.php b/projects/packages/search/src/inline-search/class-inline-search-component.php
new file mode 100644
index 0000000000000..763d6dfe9a2fb
--- /dev/null
+++ b/projects/packages/search/src/inline-search/class-inline-search-component.php
@@ -0,0 +1,113 @@
+is_search() && $query->is_main_query();
+ }
+
+ /**
+ * Register and enqueue a component stylesheet.
+ *
+ * @since $$next-version$$
+ * @param string $handle The script handle to use for the stylesheet.
+ * @param string $css_file The CSS filename (without the path).
+ * @return bool Whether the style was successfully registered and enqueued.
+ */
+ protected function register_component_style( $handle, $css_file ) {
+ $css_path = 'build/inline-search/';
+ $full_css_path = $css_path . $css_file;
+ $package_path = Package::get_installed_path();
+ $css_full_path = $package_path . '/' . $full_css_path;
+
+ // Verify the CSS file exists before trying to enqueue it
+ if ( ! file_exists( $css_full_path ) ) {
+ return false;
+ }
+
+ // We need to use plugins_url for reliable URL generation
+ $file_url = plugins_url(
+ $full_css_path,
+ $package_path . '/package.json'
+ );
+
+ // Use the file's modification time for more precise cache busting
+ $file_version = file_exists( $css_full_path ) ? filemtime( $css_full_path ) : Package::VERSION;
+
+ wp_enqueue_style(
+ $handle,
+ $file_url,
+ array(),
+ $file_version // Use file modification time for cache busting
+ );
+
+ return true;
+ }
+
+ /**
+ * Register the common inline search script if not already registered.
+ *
+ * @since $$next-version$$
+ * @return bool Whether the script was registered.
+ */
+ protected function register_inline_search_script() {
+ if ( wp_script_is( self::SCRIPT_HANDLE, 'registered' ) ) {
+ return true;
+ }
+
+ return Assets::register_script(
+ self::SCRIPT_HANDLE,
+ 'build/inline-search/jp-search-inline.js',
+ Package::get_installed_path() . '/src',
+ array(
+ 'in_footer' => true,
+ 'textdomain' => 'jetpack-search-pkg',
+ 'enqueue' => true,
+ )
+ );
+ }
+
+ /**
+ * Get the search result from the Inline_Search instance.
+ *
+ * @return array|null The search result or null if not available.
+ */
+ protected function get_search_result() {
+ $inline_search = Inline_Search::instance();
+ return $inline_search->get_search_result();
+ }
+
+ /**
+ * Convert an array of selectors to a comma-separated string for querySelector.
+ *
+ * @param array $selectors Array of CSS selectors.
+ * @return string Comma-separated string of selectors.
+ */
+ protected function format_selectors_for_query( $selectors ) {
+ return implode( ', ', $selectors );
+ }
+}
diff --git a/projects/packages/search/src/inline-search/class-inline-search-correction.php b/projects/packages/search/src/inline-search/class-inline-search-correction.php
index cfb306e8755bc..2d3d74bea4de1 100644
--- a/projects/packages/search/src/inline-search/class-inline-search-correction.php
+++ b/projects/packages/search/src/inline-search/class-inline-search-correction.php
@@ -7,21 +7,19 @@
namespace Automattic\Jetpack\Search;
-use Automattic\Jetpack\Assets;
-
/**
* Class for handling search correction display
*
* @since $$next-version$$
*/
-class Inline_Search_Correction {
+class Inline_Search_Correction extends Inline_Search_Component {
/**
* Setup hooks for displaying corrected query notice.
*
* @param \WP_Query $query The current query.
*/
public function setup_corrected_query_hooks( $query ) {
- if ( ! $query->is_search() || ! $query->is_main_query() ) {
+ if ( ! $this->is_valid_search_query( $query ) ) {
return;
}
@@ -42,8 +40,8 @@ public function enqueue_styles() {
return;
}
- $handle = 'jetpack-search-inline-corrected-query';
- $this->register_corrected_query_style( $handle );
+ $handle = self::SCRIPT_HANDLE . '-corrected-query';
+ $this->register_component_style( $handle, 'corrected-query.css' );
}
/**
@@ -57,21 +55,20 @@ public function register_corrected_query_script() {
return;
}
- $handle = 'jetpack-search-inline-corrected-query';
+ $handle = self::SCRIPT_HANDLE . '-corrected-query';
- Assets::register_script(
- $handle,
- 'build/inline-search/jp-search-inline.js',
- Package::get_installed_path() . '/src',
- array(
- 'in_footer' => true,
- 'textdomain' => 'jetpack-search-pkg',
- 'enqueue' => true,
- )
- );
+ // Don't localize if already localized to prevent duplication
+ if ( wp_script_is( $handle, 'data' ) ) {
+ $localized_data = wp_scripts()->get_data( $handle, 'data' );
+ if ( $localized_data && strpos( $localized_data, 'JetpackSearchCorrectedQuery' ) !== false ) {
+ return;
+ }
+ }
+
+ $this->register_inline_search_script();
wp_localize_script(
- $handle,
+ self::SCRIPT_HANDLE,
'JetpackSearchCorrectedQuery',
array(
'html' => $corrected_query_html,
@@ -83,41 +80,6 @@ public function register_corrected_query_script() {
);
}
- /**
- * Register and enqueue theme-specific styles for corrected query.
- *
- * @since $$next-version$$
- * @param string $handle The script handle to use for the stylesheet.
- */
- private function register_corrected_query_style( $handle ) {
- $css_path = 'build/inline-search/';
- $css_file = 'corrected-query.css';
- $full_css_path = $css_path . $css_file;
- $package_path = Package::get_installed_path();
- $css_full_path = $package_path . '/' . $full_css_path;
-
- // Verify the CSS file exists before trying to enqueue it
- if ( ! file_exists( $css_full_path ) ) {
- return;
- }
-
- // We need to use plugins_url for reliable URL generation
- $file_url = plugins_url(
- $full_css_path,
- $package_path . '/package.json'
- );
-
- // Use the file's modification time for more precise cache busting
- $file_version = file_exists( $css_full_path ) ? filemtime( $css_full_path ) : Package::VERSION;
-
- wp_enqueue_style(
- $handle,
- $file_url,
- array(),
- $file_version // Use file modification time for cache busting
- );
- }
-
/**
* Replaces the search query with the corrected query in the title.
*
@@ -183,14 +145,4 @@ private function get_corrected_query_html() {
$message
);
}
-
- /**
- * Get the search result from the Inline_Search instance.
- *
- * @return array|\WP_Error|null The search result or null if not available.
- */
- private function get_search_result() {
- $inline_search = Inline_Search::instance();
- return $inline_search->get_search_result();
- }
}
diff --git a/projects/packages/search/src/inline-search/class-inline-search-highlighter.php b/projects/packages/search/src/inline-search/class-inline-search-highlighter.php
new file mode 100644
index 0000000000000..489ade9fae6aa
--- /dev/null
+++ b/projects/packages/search/src/inline-search/class-inline-search-highlighter.php
@@ -0,0 +1,206 @@
+search_result_ids = $search_result_ids;
+ $this->highlighted_content = array();
+
+ // Process API results immediately if provided
+ if ( $results !== null ) {
+ $this->process_results( $results );
+ }
+ }
+
+ /**
+ * Set up the WordPress filters for highlighting.
+ */
+ public function setup() {
+ add_filter( 'the_title', array( $this, 'filter_highlighted_title' ), 10, 2 );
+ add_filter( 'the_content', array( $this, 'filter_highlighted_content' ), 10, 1 );
+ add_filter( 'comment_text', array( $this, 'filter_highlighted_comment' ), 10, 2 );
+ }
+
+ /**
+ * Process highlighting data for search results.
+ *
+ * @param array $results The search result data from the API.
+ */
+ public function process_results( $results ) {
+ $this->highlighted_content = array();
+
+ if ( empty( $results ) || ! is_array( $results ) ) {
+ return;
+ }
+
+ foreach ( $results as $result ) {
+ $post_id = (int) ( $result['fields']['post_id'] ?? 0 );
+ $this->process_result_highlighting( $result, $post_id );
+ }
+ }
+
+ /**
+ * Update search result IDs.
+ *
+ * @param array $search_result_ids Array of post IDs from search results.
+ */
+ public function update_search_data( $search_result_ids = array() ) {
+ $this->search_result_ids = $search_result_ids;
+ }
+
+ /**
+ * Filter the post title to show highlighted version.
+ *
+ * @param string $title The post title.
+ * @param int $post_id The post ID.
+ * @return string The filtered title.
+ */
+ public function filter_highlighted_title( $title, $post_id ) {
+ if ( ! $this->is_search_result( $post_id ) ) {
+ return $title;
+ }
+
+ if ( ! empty( $this->highlighted_content[ $post_id ]['title'] ) ) {
+ return $this->highlighted_content[ $post_id ]['title'];
+ }
+
+ return $title;
+ }
+
+ /**
+ * Filter the post content to show highlighted version.
+ *
+ * @param string $content The post content.
+ * @return string The filtered content.
+ */
+ public function filter_highlighted_content( $content ) {
+ $post_id = get_the_ID();
+
+ if ( ! $this->is_search_result( $post_id ) ) {
+ return $content;
+ }
+
+ if ( ! empty( $this->highlighted_content[ $post_id ]['content'] ) ) {
+ // Apply wpautop to maintain paragraph formatting.
+ return wpautop( $this->highlighted_content[ $post_id ]['content'] );
+ }
+
+ return $content;
+ }
+
+ /**
+ * Filter comment text to show highlighted version.
+ *
+ * @param string $comment_text The comment text.
+ * @return string The filtered comment text.
+ */
+ public function filter_highlighted_comment( $comment_text ) {
+ if ( ! is_search() || ! in_the_loop() ) {
+ return $comment_text;
+ }
+
+ $post_id = get_the_ID();
+
+ if ( ! $this->is_search_result( $post_id ) || empty( $this->highlighted_content[ $post_id ]['comments'] ) ) {
+ return $comment_text;
+ }
+
+ return $this->highlighted_content[ $post_id ]['comments'];
+ }
+
+ /**
+ * Process highlighting data for a single search result.
+ *
+ * @param array $result The search result data from the API.
+ * @param int $post_id The post ID for this result.
+ */
+ private function process_result_highlighting( $result, $post_id ) {
+ if ( empty( $result['highlight'] ) ) {
+ return;
+ }
+
+ $title = $this->extract_highlight_field( $result, 'title' );
+ $content = $this->extract_highlight_field( $result, 'content' );
+ $comments = $this->extract_highlight_field( $result, 'comments' );
+
+ $this->highlighted_content[ $post_id ] = array(
+ 'title' => $title,
+ 'content' => $content,
+ 'comments' => $comments,
+ );
+ }
+
+ /**
+ * Extract a highlight field from the search result, handling different field formats.
+ *
+ * @param array $result The search result data from the API.
+ * @param string $field The field name to extract.
+ * @return string The extracted highlighted field.
+ */
+ private function extract_highlight_field( $result, $field ) {
+ // Try exact match first
+ if ( isset( $result['highlight'][ $field ] ) && is_array( $result['highlight'][ $field ] ) && ! empty( $result['highlight'][ $field ] ) ) {
+ return $result['highlight'][ $field ][0];
+ }
+
+ // Try field variants with suffixes (e.g., 'title.default')
+ foreach ( $result['highlight'] as $key => $value ) {
+ if ( strpos( $key, $field . '.' ) === 0 ) {
+ if ( is_array( $value ) && ! empty( $value ) ) {
+ return $value[0];
+ }
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Check if the current post is a search result from our API
+ *
+ * @param int $post_id The post ID to check.
+ * @return bool Whether the post is a search result.
+ */
+ private function is_search_result( $post_id ) {
+ return is_search() && in_the_loop() && ! empty( $this->search_result_ids ) && in_array( $post_id, $this->search_result_ids, true );
+ }
+
+ /**
+ * Get the highlighted content for a post.
+ *
+ * @param int $post_id The post ID.
+ * @return array|null The highlighted content array or null if not found.
+ */
+ public function get_highlighted_content( $post_id ) {
+ return $this->highlighted_content[ $post_id ] ?? null;
+ }
+}
diff --git a/projects/packages/search/src/inline-search/class-inline-search.php b/projects/packages/search/src/inline-search/class-inline-search.php
index 7e99bbd1b02a2..9d1dd132987d8 100644
--- a/projects/packages/search/src/inline-search/class-inline-search.php
+++ b/projects/packages/search/src/inline-search/class-inline-search.php
@@ -18,13 +18,36 @@ class Inline_Search extends Classic_Search {
*/
private static $instance;
+ /**
+ * The Search Highlighter instance.
+ *
+ * @var Inline_Search_Highlighter|null
+ * @since $$next-version$$
+ */
+ private $highlighter;
+
/**
* The search correction instance.
*
- * @var Inline_Search_Correction
+ * @var Inline_Search_Correction|null
+ * @since $$next-version$$
*/
private $correction;
+ /**
+ * The colophon instance.
+ *
+ * @var Inline_Search_Colophon
+ */
+ private $colophon;
+
+ /**
+ * Stores the list of post IDs that are actual search results.
+ *
+ * @var array
+ */
+ private $search_result_ids = array();
+
/**
* Returns whether this class should be used instead of Classic_Search.
*/
@@ -50,8 +73,14 @@ public static function instance( $blog_id = null ) {
// Initialize search correction handling
self::$instance->correction = new Inline_Search_Correction();
- // Add hooks for displaying corrected query notice
+ // Initialize colophon handling
+ self::$instance->colophon = new Inline_Search_Colophon();
+
+ // Add hooks for displaying corrected query notice - only once
add_action( 'pre_get_posts', array( self::$instance->correction, 'setup_corrected_query_hooks' ) );
+
+ // Add hooks for displaying the Jetpack colophon - only once
+ add_action( 'pre_get_posts', array( self::$instance->colophon, 'setup_colophon_hooks' ) );
}
return self::$instance;
@@ -72,6 +101,17 @@ public static function get_instance_maybe_fallback_to_classic( $blog_id = null )
}
}
+ /**
+ * Set up the highlighter.
+ *
+ * @param string $blog_id The blog ID to set up for.
+ */
+ public function setup( $blog_id ) {
+ parent::setup( $blog_id );
+ // The highlighter will be initialized with data during search processing
+ $this->highlighter = null;
+ }
+
/**
* Bypass WP search and offload it to 1.3 search API instead.
*
@@ -102,24 +142,11 @@ public function filter__posts_pre_query( $posts, $query ) {
return array();
}
- $post_ids = array();
+ // Process the search results to extract post IDs and highlighted content.
+ $this->process_search_results();
- foreach ( $this->search_result['results'] as $result ) {
- $post_ids[] = (int) ( $result['fields']['post_id'] ?? 0 );
- }
-
- // Query all posts now.
- $args = array(
- 'post__in' => $post_ids,
- 'orderby' => 'post__in',
- 'perm' => 'readable',
- 'post_type' => 'any',
- 'ignore_sticky_posts' => true,
- 'suppress_filters' => true,
- 'posts_per_page' => $query->get( 'posts_per_page' ),
- );
-
- $posts_query = new \WP_Query( $args );
+ // Create a WP_Query to fetch the actual posts.
+ $posts_query = $this->create_posts_query( $query );
// WP Core doesn't call the set_found_posts and its filters when filtering posts_pre_query like we do, so need to do these manually.
$query->found_posts = $this->found_posts;
@@ -314,16 +341,31 @@ public function convert_wp_query_to_api_args( array $args ) {
}
}
+ $highlight_fields = array(
+ 'title',
+ 'content',
+ 'comments',
+ );
+
+ $fields = array(
+ 'blog_id',
+ 'post_id',
+ 'title',
+ 'content',
+ 'comments',
+ );
+
return array(
- 'blog_id' => $this->jetpack_blog_id,
- 'size' => absint( $args['posts_per_page'] ),
- 'from' => min( $from, Helper::get_max_offset() ),
- 'fields' => array( 'blog_id', 'post_id' ),
- 'query' => $args['query'] ?? '',
- 'sort' => $sort,
- 'aggregations' => empty( $aggregations ) ? null : $aggregations,
- 'langs' => $this->get_langs(),
- 'filter' => array(
+ 'blog_id' => $this->jetpack_blog_id,
+ 'size' => absint( $args['posts_per_page'] ),
+ 'from' => min( $from, Helper::get_max_offset() ),
+ 'fields' => $fields,
+ 'highlight_fields' => $highlight_fields,
+ 'query' => $args['query'] ?? '',
+ 'sort' => $sort,
+ 'aggregations' => empty( $aggregations ) ? null : $aggregations,
+ 'langs' => $this->get_langs(),
+ 'filter' => array(
'bool' => array(
'must' => $this->build_es_filters( $args ),
),
@@ -434,4 +476,61 @@ public function get_search_result(
) {
return $this->search_result;
}
+
+ /**
+ * Process search results to extract post IDs and highlighted content.
+ */
+ private function process_search_results() {
+ $post_ids = array();
+ $highlighted_results = array();
+
+ foreach ( $this->search_result['results'] as $result ) {
+ $post_id = (int) ( $result['fields']['post_id'] ?? 0 );
+ $post_ids[] = $post_id;
+
+ // Collect highlight data for processing
+ if ( ! empty( $result['highlight'] ) ) {
+ $highlighted_results[ $post_id ] = $result['highlight'];
+ }
+ }
+
+ $this->search_result_ids = $post_ids;
+ $this->highlighter = new Inline_Search_Highlighter( $post_ids );
+
+ if ( ! empty( $highlighted_results ) ) {
+ // Format highlight data for the highlighter
+ $processed_results = array();
+ foreach ( $highlighted_results as $post_id => $highlight_data ) {
+ $processed_results[] = array(
+ 'fields' => array(
+ 'post_id' => $post_id,
+ ),
+ 'highlight' => $highlight_data,
+ );
+ }
+ $this->highlighter->process_results( $processed_results );
+ }
+
+ $this->highlighter->setup();
+ }
+
+ /**
+ * Create a WP_Query to fetch the posts for search results.
+ *
+ * @param \WP_Query $original_query The original WP_Query.
+ * @return \WP_Query The new query with posts matching the search results.
+ */
+ private function create_posts_query( $original_query ) {
+ $args = array(
+ 'post__in' => $this->search_result_ids,
+ 'orderby' => 'post__in',
+ 'perm' => 'readable',
+ 'post_type' => 'any',
+ 'ignore_sticky_posts' => true,
+ 'suppress_filters' => true,
+ 'posts_per_page' => $original_query->get( 'posts_per_page' ),
+ );
+
+ return new \WP_Query( $args );
+ }
}
diff --git a/projects/packages/search/src/inline-search/js/corrected-query.js b/projects/packages/search/src/inline-search/js/corrected-query.js
deleted file mode 100644
index 94357dba61242..0000000000000
--- a/projects/packages/search/src/inline-search/js/corrected-query.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * Script to display corrected query notice after search titles.
- */
-document.addEventListener( 'DOMContentLoaded', () => {
- if ( ! window.JetpackSearchCorrectedQuery?.html ) {
- return;
- }
-
- const { selectors, html } = window.JetpackSearchCorrectedQuery;
- const titleElement = document.querySelector( selectors.join( ', ' ) );
-
- if ( ! titleElement ) {
- return;
- }
-
- titleElement.insertAdjacentHTML( 'afterend', html );
-} );
diff --git a/projects/packages/search/src/inline-search/js/index.js b/projects/packages/search/src/inline-search/js/index.js
index ef0d042935a09..43434b34f8966 100644
--- a/projects/packages/search/src/inline-search/js/index.js
+++ b/projects/packages/search/src/inline-search/js/index.js
@@ -1,6 +1,6 @@
/**
* Entry point for inline search styles.
- * This file imports the CSS and the modules for inline search.
+ * This file imports the modules for inline search.
*/
-import '../styles/corrected-query.scss';
-import './corrected-query';
+import '../styles/index.scss';
+import './inline-search';
diff --git a/projects/packages/search/src/inline-search/js/inline-search.js b/projects/packages/search/src/inline-search/js/inline-search.js
new file mode 100644
index 0000000000000..92fb435f02c84
--- /dev/null
+++ b/projects/packages/search/src/inline-search/js/inline-search.js
@@ -0,0 +1,28 @@
+/**
+ * Script to display corrected query notice after search titles and colophon at the bottom of search results.
+ */
+document.addEventListener( 'DOMContentLoaded', () => {
+ // Handle corrected query display
+ if ( window.JetpackSearchCorrectedQuery?.html ) {
+ const { selectors, html } = window.JetpackSearchCorrectedQuery;
+ const titleElement = document.querySelector( selectors.join( ', ' ) );
+ // Check if corrected query element already exists to prevent duplication
+ const correctedQueryExists = document.querySelector( '.jetpack-search-corrected-query' );
+
+ if ( titleElement && ! correctedQueryExists ) {
+ titleElement.insertAdjacentHTML( 'afterend', html );
+ }
+ }
+
+ // Handle colophon display
+ if ( window.JetpackSearchColophon?.html ) {
+ const { selector, html } = window.JetpackSearchColophon;
+ const contentElement = document.querySelector( selector );
+ // Check if colophon element already exists to prevent duplication
+ const colophonExists = document.querySelector( '.jetpack-search-inline-colophon' );
+
+ if ( contentElement && ! colophonExists ) {
+ contentElement.insertAdjacentHTML( 'beforeend', html );
+ }
+ }
+} );
diff --git a/projects/packages/search/src/inline-search/styles/colophon.scss b/projects/packages/search/src/inline-search/styles/colophon.scss
new file mode 100644
index 0000000000000..ae822c9667000
--- /dev/null
+++ b/projects/packages/search/src/inline-search/styles/colophon.scss
@@ -0,0 +1,135 @@
+$colophon-color: #555;
+$colophon-hover-color: #069E08;
+$colophon-margin-top: 2rem;
+$colophon-margin-bottom: 2rem;
+$colophon-padding-top: 1rem;
+$colophon-padding-bottom: 1rem;
+$colophon-font-size: 0.8rem;
+$colophon-logo-margin-right: 4px;
+
+.jetpack-search-inline-colophon {
+ display: flex;
+ justify-content: center;
+ margin-top: $colophon-margin-top;
+ margin-bottom: $colophon-margin-bottom;
+ padding-top: $colophon-padding-top;
+ padding-bottom: $colophon-padding-bottom;
+ font-size: $colophon-font-size;
+
+ &-link {
+ display: flex;
+ align-items: center;
+ text-decoration: none;
+ color: $colophon-color;
+
+ &:hover {
+ color: $colophon-hover-color;
+ }
+ }
+
+ &-logo {
+ margin-right: $colophon-logo-margin-right;
+ }
+}
+
+.wp-theme-pubdara,
+.wp-theme-dara-wpcom,
+.wp-theme-radcliffe-2-wpcom,
+.wp-theme-pubradcliffe,
+.wp-theme-twentyfifteen,
+.wp-theme-pubtwentyfifteen,
+.wp-theme-twentytwenty,
+.wp-theme-pubtwentytwenty,
+.wp-theme-oceanwp {
+
+ .jetpack-search-inline-colophon {
+ font-size: 1.25rem;
+ }
+}
+
+.wp-theme-varia-wpcom,
+.wp-theme-pubvaria,
+.wp-theme-lodestar-wpcom,
+.wp-theme-publodestar,
+.wp-theme-creatio-2-wpcom,
+.wp-theme-pubcreatio-2,
+.wp-theme-twentytwentythree,
+.wp-theme-pubtwentytwentythree,
+.wp-theme-twentytwentyfour,
+.wp-theme-pubtwentytwentyfour {
+
+ .jetpack-search-inline-colophon {
+ justify-content: flex-start;
+ }
+}
+
+.wp-theme-varia-wpcom,
+.wp-theme-pubvaria {
+
+ @media only screen and (min-width: 768px) {
+
+ .jetpack-search-inline-colophon {
+ margin: 0 auto;
+ max-width: calc(782px - 32px);
+ }
+ }
+}
+
+.wp-theme-twentysixteen,
+.wp-theme-pubtwentysixteen {
+
+ .jetpack-search-inline-colophon {
+ margin-top: 0;
+ padding-top: 0;
+
+ @media screen and (min-width: 56.875em) {
+ float: left;
+ justify-content: flex-start;
+ margin-right: -100px;
+ width: 70%;
+ }
+ }
+}
+
+.wp-theme-twentynineteen,
+.wp-theme-pubtwentynineteen {
+
+ .jetpack-search-inline-colophon {
+ justify-content: flex-start;
+ margin: 1rem 1rem calc(3 * 1rem);
+
+ @media only screen and (min-width: 768px) {
+ margin: 0 calc(10% + 60px) calc(3 * 1rem);
+ max-width: 80%;
+ }
+ }
+}
+
+.wp-theme-karuna-wpcom,
+.wp-theme-pubkaruna {
+
+ .jetpack-search-inline-colophon {
+ justify-content: flex-start;
+
+ @media only screen and (min-width: 768px) {
+ margin: 0 40% 0 0;
+ }
+ }
+}
+
+.wp-theme-generatepress {
+
+ .jetpack-search-inline-colophon {
+ display: block;
+ margin-top: 2rem;
+ clear: both;
+ }
+}
+
+.wp-theme-hevor {
+
+ .jetpack-search-inline-colophon {
+ justify-content: flex-start;
+ max-width: var(--wp--style--global--wide-size);
+ }
+}
diff --git a/projects/packages/search/src/inline-search/styles/index.scss b/projects/packages/search/src/inline-search/styles/index.scss
new file mode 100644
index 0000000000000..eed1d0da2ce26
--- /dev/null
+++ b/projects/packages/search/src/inline-search/styles/index.scss
@@ -0,0 +1,2 @@
+@import 'corrected-query';
+@import 'colophon';
\ No newline at end of file
diff --git a/projects/packages/search/tests/php/Inline_Search_Test.php b/projects/packages/search/tests/php/Inline_Search_Test.php
index fd89e47854152..ff59916896ece 100644
--- a/projects/packages/search/tests/php/Inline_Search_Test.php
+++ b/projects/packages/search/tests/php/Inline_Search_Test.php
@@ -104,13 +104,13 @@ public static function data_provider(): array {
'post_type' => 'any',
),
'expected_api_args' => array(
- 'size' => 5,
- 'from' => 0,
- 'fields' => array( 'post_id' ),
- 'query' => 'hello_world',
- 'sort' => 'score_recency',
- 'langs' => array( 'en_US' ),
- 'filter' => array(
+ 'size' => '5',
+ 'from' => '0',
+ 'fields' => array( 'post_id' ),
+ 'query' => 'hello_world',
+ 'sort' => 'score_recency',
+ 'langs' => array( 'en_US' ),
+ 'filter' => array(
'bool' => array(
'must' => array(
array(
@@ -121,6 +121,7 @@ public static function data_provider(): array {
),
),
),
+ 'highlight' => array( 'fields' => array( 'post_title', 'post_content' ) ),
),
),
'only_posts' => array(
@@ -130,13 +131,13 @@ public static function data_provider(): array {
'post_type' => 'post',
),
'expected_api_args' => array(
- 'size' => 5,
- 'from' => 0,
- 'fields' => array( 'post_id' ),
- 'query' => 'only search posts',
- 'sort' => 'score_recency',
- 'langs' => array( 'en_US' ),
- 'filter' => array(
+ 'size' => '5',
+ 'from' => '0',
+ 'fields' => array( 'post_id' ),
+ 'query' => 'only search posts',
+ 'sort' => 'score_recency',
+ 'langs' => array( 'en_US' ),
+ 'filter' => array(
'bool' => array(
'must' => array(
array(
@@ -147,6 +148,7 @@ public static function data_provider(): array {
),
),
),
+ 'highlight' => array( 'fields' => array( 'post_title', 'post_content' ) ),
),
),
'sort_by_date_asc' => array(
@@ -158,13 +160,13 @@ public static function data_provider(): array {
'orderby' => 'date',
),
'expected_api_args' => array(
- 'size' => 5,
- 'from' => 0,
- 'fields' => array( 'post_id' ),
- 'query' => 'search by date descending',
- 'sort' => 'date_asc',
- 'langs' => array( 'en_US' ),
- 'filter' => array(
+ 'size' => '5',
+ 'from' => '0',
+ 'fields' => array( 'post_id' ),
+ 'query' => 'search by date descending',
+ 'sort' => 'date_asc',
+ 'langs' => array( 'en_US' ),
+ 'filter' => array(
'bool' => array(
'must' => array(
array(
@@ -175,6 +177,7 @@ public static function data_provider(): array {
),
),
),
+ 'highlight' => array( 'fields' => array( 'post_title', 'post_content' ) ),
),
),
);
diff --git a/projects/plugins/jetpack/changelog/add-inline-search-branding b/projects/plugins/jetpack/changelog/add-inline-search-branding
new file mode 100644
index 0000000000000..90ef0e14a70cf
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/add-inline-search-branding
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+add-inline-search-branding
diff --git a/projects/plugins/jetpack/changelog/add-inline-search-term-highlighting b/projects/plugins/jetpack/changelog/add-inline-search-term-highlighting
new file mode 100644
index 0000000000000..ac65f026dc6bd
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/add-inline-search-term-highlighting
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Add highlighting of search term in returned search results.
diff --git a/projects/plugins/search/changelog/add-inline-search-branding b/projects/plugins/search/changelog/add-inline-search-branding
new file mode 100644
index 0000000000000..3630b8beef243
--- /dev/null
+++ b/projects/plugins/search/changelog/add-inline-search-branding
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Inline Search: add Jetpack Branding to search results.
diff --git a/projects/plugins/search/changelog/add-inline-search-term-highlighting b/projects/plugins/search/changelog/add-inline-search-term-highlighting
new file mode 100644
index 0000000000000..e8bd7beb03ccb
--- /dev/null
+++ b/projects/plugins/search/changelog/add-inline-search-term-highlighting
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Add highlighting of search term in returned search results.