mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-18 03:01:05 +01:00
Add additional help to quick search and an advanced search documentation page (#1757)
Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com>
This commit is contained in:
committed by
GitHub
parent
f2249cea73
commit
ddd2fc1055
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React, { FC, KeyboardEvent as ReactKeyboardEvent, MouseEvent, useCallback, useState, useEffect } from "react";
|
||||
import React, { FC, KeyboardEvent as ReactKeyboardEvent, MouseEvent, useCallback, useEffect, useState } from "react";
|
||||
import { Hit, Links, ValueHitField } from "@scm-manager/ui-types";
|
||||
import styled from "styled-components";
|
||||
import { BackendError, useSearch } from "@scm-manager/ui-api";
|
||||
@@ -32,10 +32,13 @@ import {
|
||||
Button,
|
||||
ErrorNotification,
|
||||
HitProps,
|
||||
LinkStyleButton,
|
||||
Notification,
|
||||
RepositoryAvatar,
|
||||
useStringHitFieldValue,
|
||||
} from "@scm-manager/ui-components";
|
||||
import SyntaxHelp from "../search/SyntaxHelp";
|
||||
import SyntaxModal from "../search/SyntaxModal";
|
||||
|
||||
const Field = styled.div`
|
||||
margin-bottom: 0 !important;
|
||||
@@ -58,6 +61,7 @@ const namespaceAndName = (hit: Hit) => {
|
||||
type HitsProps = {
|
||||
hits: Hit[];
|
||||
index: number;
|
||||
showHelp: () => void;
|
||||
gotoDetailSearch: () => void;
|
||||
clear: () => void;
|
||||
};
|
||||
@@ -68,26 +72,28 @@ type GotoProps = {
|
||||
gotoDetailSearch: () => void;
|
||||
};
|
||||
|
||||
const EmptyHits: FC<GotoProps> = ({ gotoDetailSearch }) => {
|
||||
const EmptyHits: FC = () => {
|
||||
const [t] = useTranslation("commons");
|
||||
return (
|
||||
<QuickSearchNotification>
|
||||
<Notification type="info">{t("search.quickSearch.noResults")}</Notification>
|
||||
<MoreResults gotoDetailSearch={gotoDetailSearch} />
|
||||
</QuickSearchNotification>
|
||||
<Notification className="m-4" type="info">
|
||||
{t("search.quickSearch.noResults")}
|
||||
</Notification>
|
||||
);
|
||||
};
|
||||
|
||||
type ErrorProps = {
|
||||
error: Error;
|
||||
showHelp: () => void;
|
||||
};
|
||||
|
||||
const ParseErrorNotification: FC = () => {
|
||||
const ParseErrorNotification: FC<ErrorProps> = ({ showHelp }) => {
|
||||
const [t] = useTranslation("commons");
|
||||
// TODO add link to query syntax page/modal
|
||||
return (
|
||||
<QuickSearchNotification>
|
||||
<Notification type="warning">{t("search.quickSearch.parseError")}</Notification>
|
||||
<Notification type="warning">
|
||||
<p>{t("search.quickSearch.parseError")}</p>
|
||||
<LinkStyleButton onClick={showHelp}>{t("search.quickSearch.parseErrorHelp")}</LinkStyleButton>
|
||||
</Notification>
|
||||
</QuickSearchNotification>
|
||||
);
|
||||
};
|
||||
@@ -96,10 +102,10 @@ const isBackendError = (error: Error | BackendError): error is BackendError => {
|
||||
return (error as BackendError).errorCode !== undefined;
|
||||
};
|
||||
|
||||
const SearchErrorNotification: FC<ErrorProps> = ({ error }) => {
|
||||
const SearchErrorNotification: FC<ErrorProps> = ({ error, showHelp }) => {
|
||||
// 5VScek8Xp1 is the id of sonia.scm.search.QueryParseException
|
||||
if (isBackendError(error) && error.errorCode === "5VScek8Xp1") {
|
||||
return <ParseErrorNotification />;
|
||||
return <ParseErrorNotification error={error} showHelp={showHelp} />;
|
||||
}
|
||||
return (
|
||||
<QuickSearchNotification>
|
||||
@@ -113,6 +119,9 @@ const ResultHeading = styled.h3`
|
||||
margin: 0 0.5rem;
|
||||
padding: 0.375rem 0.5rem;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const DropdownMenu = styled.div`
|
||||
@@ -153,17 +162,13 @@ const MoreResults: FC<GotoProps> = ({ gotoDetailSearch }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Hits: FC<HitsProps> = ({ hits, index, clear, gotoDetailSearch }) => {
|
||||
const HitsList: FC<HitsProps> = ({ hits, index, clear, gotoDetailSearch }) => {
|
||||
const id = useCallback(namespaceAndName, [hits]);
|
||||
const [t] = useTranslation("commons");
|
||||
|
||||
if (hits.length === 0) {
|
||||
return <EmptyHits gotoDetailSearch={gotoDetailSearch} />;
|
||||
return <EmptyHits />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div aria-expanded="true" role="listbox" className="dropdown-content">
|
||||
<ResultHeading className="dropdown-item">{t("search.quickSearch.resultHeading")}</ResultHeading>
|
||||
<>
|
||||
{hits.map((hit, idx) => (
|
||||
<div key={id(hit)} onMouseDown={(e) => e.preventDefault()} onClick={clear}>
|
||||
<Link
|
||||
@@ -175,12 +180,29 @@ const Hits: FC<HitsProps> = ({ hits, index, clear, gotoDetailSearch }) => {
|
||||
role="option"
|
||||
data-omnisearch="true"
|
||||
>
|
||||
<AvatarSection hit={hit} /> {id(hit)}
|
||||
<AvatarSection hit={hit} />
|
||||
{id(hit)}
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
<MoreResults gotoDetailSearch={gotoDetailSearch} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Hits: FC<HitsProps> = ({ showHelp, gotoDetailSearch, ...rest }) => {
|
||||
const [t] = useTranslation("commons");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div aria-expanded="true" role="listbox" className="dropdown-content">
|
||||
<ResultHeading className="dropdown-item">
|
||||
<span>{t("search.quickSearch.resultHeading")}</span>
|
||||
<SyntaxHelp onClick={showHelp} />
|
||||
</ResultHeading>
|
||||
<HitsList showHelp={showHelp} gotoDetailSearch={gotoDetailSearch} {...rest} />
|
||||
<MoreResults gotoDetailSearch={gotoDetailSearch} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -305,11 +327,12 @@ const OmniSearch: FC = () => {
|
||||
const debouncedQuery = useDebounce(query, 250);
|
||||
const { data, isLoading, error } = useSearch(debouncedQuery, { type: "repository", pageSize: 5 });
|
||||
const { showResults, hideResults, ...handlers } = useShowResultsOnFocus();
|
||||
const [showHelp, setShowHelp] = useState(false);
|
||||
const history = useHistory();
|
||||
|
||||
const clearQuery = () => {
|
||||
setQuery("");
|
||||
};
|
||||
const openHelp = () => setShowHelp(true);
|
||||
const closeHelp = () => setShowHelp(false);
|
||||
const clearQuery = () => setQuery("");
|
||||
|
||||
const gotoDetailSearch = () => {
|
||||
history.push(`/search/repository/?q=${query}`);
|
||||
@@ -320,6 +343,7 @@ const OmniSearch: FC = () => {
|
||||
|
||||
return (
|
||||
<Field className="navbar-item field">
|
||||
{showHelp ? <SyntaxModal close={closeHelp} /> : null}
|
||||
<div
|
||||
className={classNames("control", "has-icons-right", {
|
||||
"is-loading": isLoading,
|
||||
@@ -346,9 +370,15 @@ const OmniSearch: FC = () => {
|
||||
)}
|
||||
</div>
|
||||
<DropdownMenu className="dropdown-menu" onMouseDown={(e) => e.preventDefault()}>
|
||||
{error ? <SearchErrorNotification error={error} /> : null}
|
||||
{error ? <SearchErrorNotification error={error} showHelp={openHelp} /> : null}
|
||||
{!error && data ? (
|
||||
<Hits gotoDetailSearch={gotoDetailSearch} clear={clearQuery} index={index} hits={data._embedded.hits} />
|
||||
<Hits
|
||||
showHelp={openHelp}
|
||||
gotoDetailSearch={gotoDetailSearch}
|
||||
clear={clearQuery}
|
||||
index={index}
|
||||
hits={data._embedded.hits}
|
||||
/>
|
||||
) : null}
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user