mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-01-27 01:39:09 +01:00
Optimize global search result view (#2107)
Enhance search result view by sorting the categories after their translation (repositories will still be sticky on top). Further disable categories with no search results and be more explicit with the text displayed if no search results were found.
This commit is contained in:
committed by
GitHub
parent
56ace2811b
commit
3e236fe5ac
2
gradle/changelog/optimize_global_search.yaml
Normal file
2
gradle/changelog/optimize_global_search.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: changed
|
||||
description: Enhance search result view by sorting translated categories and disable categories with no search results ([#2107](https://github.com/scm-manager/scm-manager/pull/2107))
|
||||
@@ -203,7 +203,7 @@
|
||||
"subtitle": "{{type}} Ergebnisse für \"{{query}}\"",
|
||||
"subtitleWithContext": "{{type}} Ergebnisse für \"{{query}}\" in \"{{context}}\"",
|
||||
"types": "Ergebnisse",
|
||||
"noHits": "Die Suche ergab keine Treffer",
|
||||
"noHits": "Keine Ergebnisse gefunden. Auf der rechten Seite finden Sie weitere Kategorien, in denen Ergebnisse vorhanden sein könnten",
|
||||
"syntaxHelp": "Finden Sie bessere Ergebnisse durch die Nutzung der vollen <0>Such-Syntax</0>",
|
||||
"quickSearch": {
|
||||
"resultHeading": "Hilfe beim Suchen?",
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
"subtitle": "{{type}} results for \"{{query}}\"",
|
||||
"subtitleWithContext": "{{type}} results for \"{{query}}\" in \"{{context}}\"",
|
||||
"types": "Results",
|
||||
"noHits": "No results found",
|
||||
"noHits": "No results found. On the right side you will find other categories where results might be available",
|
||||
"syntaxHelp": "Find better results by using the full <0>search syntax</0>",
|
||||
"quickSearch": {
|
||||
"resultHeading": "Need help?",
|
||||
|
||||
@@ -95,7 +95,7 @@ const AvatarSection: FC<{ repository: Repository }> = ({ repository }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const HitsList: FC<Omit<HitsProps, "showHelp" | "hits">> = ({ entries }) => {
|
||||
const HitList: FC<Omit<HitsProps, "showHelp" | "hits">> = ({ entries }) => {
|
||||
return (
|
||||
<ul id="omni-search-results" aria-expanded="true" role="listbox">
|
||||
{entries}
|
||||
@@ -155,7 +155,7 @@ const Hits: FC<HitsProps> = ({ entries, hits, showHelp, ...rest }) => {
|
||||
<>
|
||||
<div className="dropdown-content p-0">
|
||||
<ScreenReaderHitSummary hits={hits} />
|
||||
<HitsList entries={entries} {...rest} />
|
||||
<HitList entries={entries} {...rest} />
|
||||
<ResultHeading
|
||||
className={classNames(
|
||||
"dropdown-item",
|
||||
@@ -352,7 +352,7 @@ const OmniSearch: FC = () => {
|
||||
namespaceContext: context.namespace || "",
|
||||
repositoryNameContext: context.name || "",
|
||||
});
|
||||
searchTypes.sort(orderTypes);
|
||||
searchTypes.sort(orderTypes(t));
|
||||
|
||||
const id = useCallback(namespaceAndName, []);
|
||||
|
||||
@@ -362,6 +362,7 @@ const OmniSearch: FC = () => {
|
||||
if (context.namespace && context.name && searchTypes.length > 0) {
|
||||
newEntries.push(
|
||||
<HitEntry
|
||||
key="search.quickSearch.searchRepo"
|
||||
selected={newEntries.length === index}
|
||||
clear={clearQuery}
|
||||
label={t("search.quickSearch.searchRepo")}
|
||||
@@ -372,6 +373,7 @@ const OmniSearch: FC = () => {
|
||||
if (context.namespace) {
|
||||
newEntries.push(
|
||||
<HitEntry
|
||||
key="search.quickSearch.searchNamespace"
|
||||
selected={newEntries.length === index}
|
||||
clear={clearQuery}
|
||||
label={t("search.quickSearch.searchNamespace")}
|
||||
@@ -381,6 +383,7 @@ const OmniSearch: FC = () => {
|
||||
}
|
||||
newEntries.push(
|
||||
<HitEntry
|
||||
key="search.quickSearch.searchEverywhere"
|
||||
selected={newEntries.length === index}
|
||||
clear={clearQuery}
|
||||
label={t("search.quickSearch.searchEverywhere")}
|
||||
@@ -391,7 +394,7 @@ const OmniSearch: FC = () => {
|
||||
hits?.forEach((hit, idx) => {
|
||||
newEntries.push(
|
||||
<HitEntry
|
||||
key={idx}
|
||||
key={`search.quickSearch.hit${idx}`}
|
||||
selected={length + idx === index}
|
||||
clear={clearQuery}
|
||||
label={id(hit)}
|
||||
|
||||
@@ -49,12 +49,12 @@ const Results: FC<Props> = ({ result, type, page, query }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="panel">
|
||||
<Hits type={type} hits={hits} />
|
||||
<div className="panel-footer">
|
||||
<LinkPaginator collection={result} page={page} filter={query} />
|
||||
<>
|
||||
<div className="panel">
|
||||
<Hits type={type} hits={hits} />
|
||||
</div>
|
||||
</div>
|
||||
<LinkPaginator collection={result} page={page} filter={query} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -34,11 +34,18 @@ import {
|
||||
urls,
|
||||
} from "@scm-manager/ui-components";
|
||||
import { Link, useLocation, useParams } from "react-router-dom";
|
||||
import { useSearch, useSearchCounts, useSearchTypes, useNamespaceAndNameContext } from "@scm-manager/ui-api";
|
||||
import { useNamespaceAndNameContext, useSearch, useSearchCounts, useSearchTypes } from "@scm-manager/ui-api";
|
||||
import Results from "./Results";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import SearchErrorNotification from "./SearchErrorNotification";
|
||||
import SyntaxModal from "./SyntaxModal";
|
||||
import type { TFunction } from "i18next";
|
||||
import styled from "styled-components";
|
||||
|
||||
const DisabledNavLink = styled.div`
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
`;
|
||||
|
||||
type PathParams = {
|
||||
type: string;
|
||||
@@ -80,17 +87,16 @@ const usePageParams = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const orderTypes = (left: string, right: string) => {
|
||||
if (left === "repository" && right !== "repository") {
|
||||
export const orderTypes = (t: TFunction) => (a: string, b: string) => {
|
||||
if (!a || !b) {
|
||||
return 0;
|
||||
}
|
||||
if (a === "repository" && b !== "repository") {
|
||||
return -1;
|
||||
} else if (left !== "repository" && right === "repository") {
|
||||
return 1;
|
||||
} else if (left < right) {
|
||||
return -1;
|
||||
} else if (left > right) {
|
||||
} else if (a !== "repository" && b === "repository") {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return t(`plugins:search.types.${a}.navItem`, a)?.localeCompare(t(`plugins:search.types.${b}.navItem`, b)) ?? 0;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
@@ -144,7 +150,7 @@ const Search: FC = () => {
|
||||
};
|
||||
const { data, isLoading, error } = useSearch(query, searchOptions);
|
||||
const types = useSearchTypes(searchOptions);
|
||||
types.sort(orderTypes);
|
||||
types.sort(orderTypes(t));
|
||||
|
||||
const searchCounts = useSearchCounts(
|
||||
types.filter((type) => type !== selectedType),
|
||||
@@ -174,27 +180,44 @@ const Search: FC = () => {
|
||||
<Results result={data} query={query} page={page} type={selectedType} />
|
||||
</PrimaryContentColumn>
|
||||
<SecondaryNavigation label={t("search.types")} collapsible={false}>
|
||||
{types.map((type) => (
|
||||
<NavLink
|
||||
key={type}
|
||||
to={`/search/${type}/?q=${query}${namespace ? "&namespace=" + namespace : ""}${
|
||||
name ? "&name=" + name : ""
|
||||
}`}
|
||||
label={type}
|
||||
activeOnlyWhenExact={false}
|
||||
>
|
||||
<Level
|
||||
left={t(`plugins:search.types.${type}.navItem`, type)}
|
||||
right={
|
||||
<Count
|
||||
isLoading={counts[type].isLoading}
|
||||
isSelected={type === selectedType}
|
||||
count={counts[type].data}
|
||||
{types.map((type) =>
|
||||
type !== selectedType && (counts[type].isLoading || counts[type].data === 0) ? (
|
||||
<li>
|
||||
<DisabledNavLink className="p-4 is-unselectable">
|
||||
<Level
|
||||
left={t(`plugins:search.types.${type}.navItem`, type)}
|
||||
right={
|
||||
<Count
|
||||
isLoading={counts[type].isLoading}
|
||||
isSelected={type === selectedType}
|
||||
count={counts[type].data}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</NavLink>
|
||||
))}
|
||||
</DisabledNavLink>
|
||||
</li>
|
||||
) : (
|
||||
<NavLink
|
||||
key={type}
|
||||
to={`/search/${type}/?q=${query}${namespace ? "&namespace=" + namespace : ""}${
|
||||
name ? "&name=" + name : ""
|
||||
}`}
|
||||
label={type}
|
||||
activeOnlyWhenExact={false}
|
||||
>
|
||||
<Level
|
||||
left={t(`plugins:search.types.${type}.navItem`, type)}
|
||||
right={
|
||||
<Count
|
||||
isLoading={counts[type].isLoading}
|
||||
isSelected={type === selectedType}
|
||||
count={counts[type].data}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</NavLink>
|
||||
)
|
||||
)}
|
||||
</SecondaryNavigation>
|
||||
</CustomQueryFlexWrappedColumns>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user