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

Search improvements #469

Merged
merged 6 commits into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { GetProductLogo } from '@/../../packages/ui/common/assets';
import { truncateString } from '@/../../packages/ui/common/text-util';
import { toClass, truncateString } from '@/../../packages/ui/common/text-util';
import { ActionPropPayload, ItemIndexActionPayload, PreviewSearchSuggestionQuery, SearchResponseSuggestion, WidgetAction, WidgetDataType, usePreviewSearch, widget } from '@sitecore-search/react';
import { ArticleCard, NavMenu, Presence } from '@sitecore-search/ui';
import type { PreviewSearchActionProps } from '@sitecore-search/widgets';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { SyntheticEvent, useCallback, useState } from 'react';
import Loader from './Loader';
import { Loading } from 'ui/components/common/Loading';

type ArticleModel = {
id: string;
Expand All @@ -20,15 +20,15 @@ type ArticleModel = {
site_name: string;
highlight: {
description: string;
}
};
};

const Articles = ({ loading = false, articles, onItemClick, suggestionsReturned }: { loading?: boolean; articles: Array<ArticleModel>; onItemClick: PreviewSearchActionProps['onItemClick']; suggestionsReturned?: boolean}) => (
<NavMenu.Content className={'bg-theme-bg text-theme-text border-theme-border absolute right-0 top-0 inline-block overflow-y-auto border-b border-r ' + (suggestionsReturned ? ' w-4/5 h-full' : ' w-5/5')}>
const Articles = ({ loading = false, articles, onItemClick, suggestionsReturned }: { loading?: boolean; articles: Array<ArticleModel>; onItemClick: PreviewSearchActionProps['onItemClick']; suggestionsReturned?: boolean }) => (
<NavMenu.Content className={'bg-theme-bg text-theme-text absolute right-0 top-0 hidden overflow-hidden overflow-y-auto md:inline-block ' + (suggestionsReturned ? 'h-fit w-4/5' : ' w-5/5')}>
<Presence present={loading}>
<Loader />
<Loading />
</Presence>
<NavMenu.List className="mr-0 grid list-none grid-cols-3 gap-0 p-2">
<NavMenu.List className="mr-0 grid list-none gap-0 p-2 md:grid-cols-3">
{!loading &&
articles.map((article, index) => (
<NavMenu.Item key={`${article.id}@${article.source_id}`} className="mx-2 my-4 inline">
Expand All @@ -41,11 +41,11 @@ const Articles = ({ loading = false, articles, onItemClick, suggestionsReturned
window.open(article.url, '_blank');
}}
>
<ArticleCard.Root className="bg-theme-bg grid grid-cols-4 items-center">
<ArticleCard.Root className="grid items-center sm:grid-cols-1 md:grid-cols-4">
<div className={`${article.type == 'Video' ? 'col-span-3' : 'col-span-4'} pr-2`}>
<ArticleCard.Title className="text-base font-bold group-hover:underline">{article.name}</ArticleCard.Title>
<div className="my-2">
{article.type && <span className="bg-primary-500 text-2xs px-2.5 py-1 uppercase text-white dark:bg-teal-500">{article.type}</span>}
{article.type && <span className={`result-type-${toClass(article.type)} bg-primary-500 text-2xs px-2.5 py-1 uppercase text-white dark:bg-teal-500`}>{article.type}</span>}
{article.index_name && <span className="text-2xs mr-2 w-full px-2.5 py-1 uppercase">{article.site_name}</span>}
</div>
{article.type == 'Video' && (
Expand All @@ -54,8 +54,8 @@ const Articles = ({ loading = false, articles, onItemClick, suggestionsReturned
{!article.image_url && <Image width={256} height={144} src="/images/social/social-card-default.jpeg" alt={article.index_name} className="object-scale-down" />}
</div>
)}
{article.type != 'Video' && article?.highlight?.description && <p className="text-xs" dangerouslySetInnerHTML={{ __html: truncateString(article.highlight.description, 300, true) }} />}
{article.type != 'Video' && !article.highlight && article.description && <p className="text-xs">{truncateString(article.description, 300, true)}</p>}
{article.type != 'Video' && article?.highlight?.description && <p className="text-xs" dangerouslySetInnerHTML={{ __html: truncateString(article.highlight.description, 100, true) }} />}
{article.type != 'Video' && !article.highlight && article.description && <p className="text-xs">{truncateString(article.description, 100, true)}</p>}
</div>
</ArticleCard.Root>
</NavMenu.Link>
Expand All @@ -78,6 +78,7 @@ const Group = ({
activeItem,
onActiveItem,
onItemClick,
onGroupTitleClick,
}: {
groupTitle: string;
groupId: string;
Expand All @@ -86,22 +87,25 @@ const Group = ({
activeItem: string;
onActiveItem: (arg: string) => void;
onItemClick: (payload: ActionPropPayload<SearchItemClickedAction>) => void;
onGroupTitleClick: (arg: string) => void;
}) => {
return (
<div className="bg-primary-100 border-theme-border h-96 w-1/5 border-b border-l pt-2 dark:bg-teal-900">
<div className="md:bg-primary-100 sm:bg-theme-bg border-theme-border sm:1/3 border-b border-l pt-2 dark:bg-teal-900 md:w-1/5">
<h2 className="m-4 box-border text-left font-semibold uppercase">{groupTitle}</h2>
{articles.map(({ text }) => (
<NavMenu.Item value={getGroupId(groupId, text)} key={text} className=" hover:text-primary-900 overflow-hidden pl-4 hover:bg-white dark:bg-teal-900 dark:hover:bg-teal-700 dark:hover:text-white">
<NavMenu.Trigger
className="relative inline-block py-1 text-left"
className=" py-1 text-left"
onMouseOver={(e) => {
const target = e.target as HTMLLinkElement;
target.focus();
}}
onFocus={() => onActiveItem(getGroupId(groupId, text))}
onClick={() => onGroupTitleClick(text)}
>
{text}
</NavMenu.Trigger>

<PreviewSearchSuggestionQuery<ArticleModel> active={activeItem === getGroupId(groupId, text)} value={text} filterAttribute={filterAttribute}>
{({ queryResult: { isFetching, data: { content: articles = [] } = {} } }) => <Articles loading={isFetching} articles={articles} onItemClick={onItemClick} suggestionsReturned={true} />}
</PreviewSearchSuggestionQuery>
Expand All @@ -116,32 +120,24 @@ const getGroupId = (name: string, value: string) => `${name}@${value}`;
const PreviewSearchInput = ({ defaultProductsPerPage = 6 }) => {
const router = useRouter();
const indexSources = process.env.NEXT_PUBLIC_SEARCH_SOURCES?.split(',') || [];
const { q } = router.query
const { q } = router.query;
const {
context: { keyphrase = q || '' },
actions: { onItemClick, onKeyphraseChange },
queryResult: {
isFetching,
isLoading,
data: {
content: articles = [],
suggestion: {
name_suggester: articleSuggestions = [],
} = {},
} = {},
},
queryResult: { isFetching, isLoading, data: { content: articles = [], suggestion: { name_suggester: articleSuggestions = [] } = {} } = {} },
} = usePreviewSearch<ArticleModel>((query) => {
query.getRequest().setSearchQueryHighlight({
fields: ['description'],
fragment_size: 100,
pre_tag: '<strong>',
post_tag: '</strong>',
}).setSources(indexSources);

query
.getRequest()
.setSearchQueryHighlight({
fields: ['description'],
fragment_size: 100,
pre_tag: '<strong>',
post_tag: '</strong>',
})
.setSources(indexSources);

return {
suggestionsList: [
{ suggestion: 'name_suggester', max: 10 },
],
suggestionsList: [{ suggestion: 'name_suggester', max: 10 }],
itemsPerPage: defaultProductsPerPage,
};
});
Expand Down Expand Up @@ -172,6 +168,11 @@ const PreviewSearchInput = ({ defaultProductsPerPage = 6 }) => {
router.push('/search?q=' + encodeURIComponent(target.value)).then(() => router.reload());
};

function onGroupTitleClick(arg: string): void {
setValue('');
router.push('/search?q=' + encodeURIComponent(arg)).then(() => router.reload());
}

return (
<NavMenu.Root onValueChange={onValueChange} value={value}>
<NavMenu.List>
Expand All @@ -193,7 +194,8 @@ const PreviewSearchInput = ({ defaultProductsPerPage = 6 }) => {
onChange={keyphraseHandler}
placeholder="What are you looking for?"
onFocus={() => {
if (keyphrase.length > 0) {
if (keyphrase.length > 0 && window.innerWidth <= 691) {
console.log({ window });
markvanaalst marked this conversation as resolved.
Show resolved Hide resolved
setActiveItem('defaultArticlesResults');
}
}}
Expand All @@ -208,15 +210,17 @@ const PreviewSearchInput = ({ defaultProductsPerPage = 6 }) => {
</div>
</form>

<NavMenu.Content className="bg-theme-bg text-theme-text absolute left-0 top-10 inline-block h-fit w-full justify-center pt-0 shadow-md">
<NavMenu.Content className="bg-theme-bg text-theme-text relative left-0 inline-block w-full justify-center border-b border-r pt-0 shadow-md">
<Presence present={loading}>
<Loader />
<Loading />
</Presence>
{!loading && (
<NavMenu.SubContent orientation="vertical" value={activeItem} className="box-border block w-full">
<NavMenu.List className="w-full">
{articleSuggestions.length > 0 && <Group groupTitle="Suggested Terms" groupId="keyphrase" articles={articleSuggestions} onItemClick={onItemClick} activeItem={activeItem} onActiveItem={setActiveItem} />}
<NavMenu.Item value="defaultArticlesResults" key="defaultArticlesResults" className="b-0 bg-none">
<NavMenu.List className="">
{articleSuggestions.length > 0 && (
<Group groupTitle="Suggested Terms" groupId="keyphrase" articles={articleSuggestions} onItemClick={onItemClick} onGroupTitleClick={onGroupTitleClick} activeItem={activeItem} onActiveItem={setActiveItem} />
)}
<NavMenu.Item value="defaultArticlesResults" key="defaultArticlesResults" className="b-0 bg-none">
<NavMenu.Trigger aria-hidden className="hidden" />
<Articles articles={articles} onItemClick={onItemClick} suggestionsReturned={articleSuggestions.length > 0} />
</NavMenu.Item>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
Search results page
*/

.result-type-base {
@apply text-white;
}

.result-type-article {
@apply bg-primary-500 dark:bg-primary-900 result-type-base;
}
.result-type-forum {
@apply result-type-base bg-teal-500 dark:bg-teal-500;
}
.result-type-repository {
@apply result-type-base bg-gray-500;
}
.result-type-video {
@apply result-type-base bg-red-500;
}
.result-type-changelog {
@apply result-type-base bg-orange-500;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { useSearchResults, widget, WidgetDataType } from '@sitecore-search/react
import { WidgetComponentProps } from '@sitecore-search/react/types';
import Image from 'next/image';
import { ComponentType } from 'react';
import { truncateString } from 'ui/common/text-util';
import Loader from './Loader';
import { toClass, truncateString } from 'ui/common/text-util';
import { Loading } from 'ui/components/common/Loading';
import QuerySummary from './QuerySummary';
import SearchFacets from './SearchFacets';
import SearchPagination from './SearchPagination';
Expand All @@ -24,12 +24,15 @@ export const SearchResults = (props: SearchResultsType) => {
context: { page = currentPage, itemsPerPage = initialArticlesPerPage, sortType = defaultSortType },
queryResult: { isLoading, data: { sort: { choices: sortChoices = [] } = {}, total_item: totalItems = 0, content: articles = [], facet: facets = [] } = {} },
} = useSearchResults((query) => {
query.getRequest().setSearchQueryHighlight({
fields: ['description'],
fragment_size: 100,
pre_tag: '<strong>',
post_tag: '</strong>',
}).setSources(indexSources);
query
.getRequest()
.setSearchQueryHighlight({
fields: ['description'],
fragment_size: 100,
pre_tag: '<strong>',
post_tag: '</strong>',
})
.setSources(indexSources);

return {
itemsPerPage: initialArticlesPerPage,
Expand All @@ -43,12 +46,12 @@ export const SearchResults = (props: SearchResultsType) => {
<>
{isLoading && (
<div className="pt-10">
<Loader />
<Loading />
</div>
)}
{!isLoading && (
<div className="mt-6 grid gap-6 md:grid-cols-3">
{articles.length > 0 &&
{articles.length > 0 && (
<>
<div className="md:col-span-1">
<SearchFacets onFacetClick={onFacetClick} facets={facets} />
Expand All @@ -63,44 +66,48 @@ export const SearchResults = (props: SearchResultsType) => {
</div>

<ul className="border-theme-border mt-2 border-t">
{articles.length > 0 && articles.map((result, index) => (
<li key={index} className="mt-2 py-4">
<a href={result.url} className="group" onClick={(e) => {
{articles.length > 0 &&
articles.map((result, index) => (
<li key={index} className="mt-2 py-4">
<a
href={result.url}
className="group"
onClick={(e) => {
e.preventDefault();
onItemClick({ id: result.id || '', index });
window.open(result.url, '_blank');
}}>
<div className="bg-theme-bg grid grid-cols-4 items-center md:flex-row">
<div className={`${result.type == 'Video' ? 'col-span-3' : 'col-span-4'} pr-2`}>
{result.type && <span className="bg-primary-500 text-2xs px-2.5 py-1 uppercase text-white dark:bg-teal-500">{result.type}</span>}
{result.index_name && <span className="text-2xs mr-2 px-2.5 py-1 uppercase">{result.site_name}</span>}
<h3 className="mt-2 text-base font-bold group-hover:underline">{result.name}</h3>
}}
>
<div className="bg-theme-bg grid grid-cols-4 items-center md:flex-row">
<div className={`${result.type == 'Video' ? 'col-span-3' : 'col-span-4'} pr-2`}>
{result.type && <span className={`result-type-${toClass(result.type)} text-2xs px-2.5 py-1 uppercase`}>{result.type}</span>}
{result.index_name && <span className="text-2xs mr-2 px-2.5 py-1 uppercase">{result.site_name}</span>}
<h3 className="mt-2 text-base font-bold group-hover:underline">{result.name}</h3>

{result?.highlight?.description && <p className="text-sm" dangerouslySetInnerHTML={{ __html: truncateString(result.highlight.description, 300, true) }} />}
{!result.highlight && result.description && <p className="text-sm">{truncateString(result.description, 300, true)}</p>}
</div>
{result.type == 'Video' && (
<div className="col-span-1">
{result.image_url && <Image width={256} height={144} src={result.image_url} alt={result.index_name} className="mt-20 object-scale-down" />}
{!result.image_url && <Image width={256} height={144} src="/images/social/social-card-default.jpeg" alt={result.index_name} className="mt-20 object-scale-down" />}
{result?.highlight?.description && <p className="text-sm" dangerouslySetInnerHTML={{ __html: truncateString(result.highlight.description, 300, true) }} />}
{!result.highlight && result.description && <p className="text-sm">{truncateString(result.description, 300, true)}</p>}
</div>
)}
</div>
<span className="text-violet mt-1 block break-words text-xs italic dark:text-white">{result.url}</span>
</a>
</li>
))}

{result.type == 'Video' && (
<div className="col-span-1">
{result.image_url && <Image width={256} height={144} src={result.image_url} alt={result.index_name} className="mt-20 object-scale-down" />}
{!result.image_url && <Image width={256} height={144} src="/images/social/social-card-default.jpeg" alt={result.index_name} className="mt-20 object-scale-down" />}
</div>
)}
</div>
<span className="text-violet mt-1 block break-words text-xs italic dark:text-white">{result.url}</span>
</a>
</li>
))}
</ul>

<SearchPagination defaultCurrentPage={1} onPageNumberChange={(v) => onPageNumberChange({ page: v })} page={page} pageSize={itemsPerPage} totalItems={totalItems} />
</div>
</>
}
{articles.length === 0 &&
<p className="md:col-span-3">Your search terms did not return any results, please use the input above to try again.</p>
}
)}
{articles.length === 0 && <p className="md:col-span-3">Your search terms did not return any results, please use the input above to try again.</p>}
</div>
)}
)}
</>
);
};
Expand Down
1 change: 1 addition & 0 deletions apps/devportal/src/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
@import 'ui/styles/global.css';

@import '../components/changelog/Changelog.styles.css';
@import '../components/integrations/sitecore-search/Search.styles.css';
8 changes: 8 additions & 0 deletions packages/ui/common/text-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ export function truncateString(str: string, maxLength: number, appendMoreIndiati

return returnValue;
}

export function toClass(text: string): string {
return text
.toLowerCase()
.replace(/[^\w\s-.]/g, '') // remove non-alphanumeric characters except the period
.replace(/[\s_-]+/g, '-') // replace spaces, underscores, or hyphens with a single hyphen
.trim();
}