Skip to content

Commit

Permalink
Add algolia recommendations (forem#21189)
Browse files Browse the repository at this point in the history
* Add algolia recommendations

* Add recommend

* Add conditional
  • Loading branch information
benhalpern authored Jul 31, 2024
1 parent 767e394 commit 7ff071b
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 24 deletions.
72 changes: 49 additions & 23 deletions app/javascript/Search/SearchForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { h } from 'preact';
import { forwardRef, useState, useEffect, useRef, useMemo, useCallback } from 'preact/compat';
import PropTypes from 'prop-types';
import algoliasearch from 'algoliasearch/lite';
import recommend from '@algolia/recommend'; // Ensure this is the correct import
import { locale } from '../utilities/locale';
import { ButtonNew as Button } from '@crayons';
import SearchIcon from '@images/search.svg';
Expand All @@ -16,19 +17,56 @@ export const SearchForm = forwardRef(
const env = 'production';
const client = useMemo(() => (algoliaId ? algoliasearch(algoliaId, algoliaSearchKey) : null), [algoliaId, algoliaSearchKey]);
const index = useMemo(() => (client ? client.initIndex(`Article_${env}`) : null), [client]);
const recommendClient = useMemo(() => (algoliaId ? recommend(algoliaId, algoliaSearchKey) : null), [algoliaId, algoliaSearchKey]);
const articleContainer = document.getElementById('article-show-container');

const [inputValue, setInputValue] = useState(searchTerm);
const [suggestions, setSuggestions] = useState([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(-1);
const suggestionsRef = useRef();

// eslint-disable-next-line react-hooks/exhaustive-deps
// Handle clicks outside the dropdown
const handleClickOutside = useCallback((event) => {
if (
suggestionsRef.current &&
!suggestionsRef.current.contains(event.target) &&
!ref.current.contains(event.target)
) {
setShowSuggestions(false);
}
}, [ref]);

useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [handleClickOutside]);

// Fetch initial recommendations
const fetchRecommendations = useCallback(() => {
if (recommendClient && articleContainer?.dataset?.articleId) {
recommendClient.getRelatedProducts([
{
indexName: `Article_${env}`,
objectID: articleContainer?.dataset?.articleId,
maxRecommendations: 5,
threshold: 10,
},
]).then(({ results }) => {
setSuggestions(results[0].hits);
})
}
}, [recommendClient]);

// Debounced search function
const debouncedSearch = useCallback(debounceAction((value) => {
if (value && index) {
index.search(value, { hitsPerPage: 5 }).then(({ hits }) => {
setSuggestions(hits); // Assuming 'title' is the field to display
});
} else {
} else if (!articleContainer?.dataset?.articleId) {
setSuggestions([]);
}
}, 200), [index]);
Expand All @@ -42,6 +80,9 @@ export const SearchForm = forwardRef(
setInputValue(e.target.value);
setShowSuggestions(true);
setActiveSuggestionIndex(-1);
if (e.target.value.length === 0 && articleContainer) {
fetchRecommendations();
}
};

// Handle keyboard navigation and selection
Expand Down Expand Up @@ -72,24 +113,6 @@ export const SearchForm = forwardRef(
}
};

// Handle clicks outside the dropdown
const handleClickOutside = (event) => {
if (
suggestionsRef.current &&
!suggestionsRef.current.contains(event.target) &&
!ref.current.contains(event.target)
) {
setShowSuggestions(false);
}
};

useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);

return (
<form
action="/search"
Expand All @@ -110,7 +133,7 @@ export const SearchForm = forwardRef(
className="crayons-header--search-input crayons-textfield"
type="text"
name="q"
placeholder={`${locale('core.search')}...`}
placeholder={articleContainer?.dataset?.articleId ? 'Find related posts...' : `${locale('core.search')}...`}
autoComplete="off"
aria-label="Search term"
value={inputValue}
Expand All @@ -121,6 +144,9 @@ export const SearchForm = forwardRef(
typeahead.classList.remove('hidden');
}
setShowSuggestions(true);
if (articleContainer) {
fetchRecommendations();
}
}}
onKeyDown={handleKeyDown}
/>
Expand Down Expand Up @@ -156,8 +182,8 @@ export const SearchForm = forwardRef(
))}
<div class="crayons-header--search-typeahead-footer">
<span>
Displaying Posts — Submit search to filter by Users,
Comments, etc.
{ inputValue.length > 0 ? 'Submit search for advanced filtering.' : 'Displaying Algolia Recommendations — Start typing to search' }

</span>
<a
href="https://www.algolia.com/developers/?utm_source=devto&utm_medium=referral"
Expand Down
3 changes: 3 additions & 0 deletions app/javascript/packs/Search.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ document.addEventListener('DOMContentLoaded', () => {
const root = document.getElementById('header-search');

render(<SearchFormSync />, root);
window.InstantClick.on('change', () => {
render(<SearchFormSync />, root);
});
});
2 changes: 1 addition & 1 deletion app/views/layouts/_top_bar.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<form accept-charset="UTF-8" method="get" action="<%= search_path %>" role="search">
<div class="crayons-fields crayons-fields--horizontal">
<div class="crayons-field flex-1 relative">
<input id="search-input" class="crayons-header--search-input crayons-textfield js-search-input" type="text" id="nav-search" name="q" placeholder="<%= t("views.search.placeholder") %>" autocomplete="off" />
<input id="search-input" class="crayons-header--search-input crayons-textfield js-search-input" type="text" id="nav-search" name="q" placeholder="<%= @article.present? ? "Find related posts" : t("views.search.placeholder") %>" autocomplete="off" />
<button type="submit" aria-label="<%= t("views.search.icon.aria_label") %>" class="c-btn c-btn--icon-alone absolute inset-px right-auto mt-0 py-0">
<%= crayons_icon_tag(:search, aria_hidden: true, title: t("views.search.icon.title")) %>
</button>
Expand Down

0 comments on commit 7ff071b

Please sign in to comment.