+import {
+ commentsToFlatNodes,
+ communityToChoice,
+ enableDownvotes,
+ enableNsfw,
+ fetchCommunities,
+ fetchUsers,
+ getUpdatedSearchId,
+ myAuth,
+ personToChoice,
+ setIsoData,
+ showLocal,
+} from "@utils/app";
+import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
+import {
+ capitalizeFirstLetter,
+ debounce,
+ getIdFromString,
+ getPageFromString,
+ getQueryParams,
+ getQueryString,
+ numToSI,
+} from "@utils/helpers";
+import type { QueryParams } from "@utils/types";
+import { Choice, RouteDataResponse } from "@utils/types";
import type { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import {
SearchType,
SortType,
} from "lemmy-js-client";
-import { i18n } from "../i18next";
+import { fetchLimit } from "../config";
import { CommentViewType, InitialFetchRequest } from "../interfaces";
-import { FirstLoadService } from "../services/FirstLoadService";
+import { FirstLoadService, I18NextService } from "../services";
import { HttpService, RequestState } from "../services/HttpService";
-import {
- Choice,
- QueryParams,
- RouteDataResponse,
- capitalizeFirstLetter,
- commentsToFlatNodes,
- communityToChoice,
- debounce,
- enableDownvotes,
- enableNsfw,
- fetchCommunities,
- fetchLimit,
- fetchUsers,
- getIdFromString,
- getPageFromString,
- getQueryParams,
- getQueryString,
- getUpdatedSearchId,
- myAuth,
- numToSI,
- personToChoice,
- restoreScrollPosition,
- saveScrollPosition,
- setIsoData,
- showLocal,
-} from "../utils";
import { CommentNodes } from "./comment/comment-nodes";
import { HtmlTags } from "./common/html-tags";
import { Spinner } from "./common/icon";
}
type SearchData = RouteDataResponse<{
- communityResponse?: GetCommunityResponse;
- listCommunitiesResponse?: ListCommunitiesResponse;
- creatorDetailsResponse?: GetPersonDetailsResponse;
- searchResponse?: SearchResponse;
- resolveObjectResponse?: ResolveObjectResponse;
+ communityResponse: GetCommunityResponse;
+ listCommunitiesResponse: ListCommunitiesResponse;
+ creatorDetailsResponse: GetPersonDetailsResponse;
+ searchResponse: SearchResponse;
+ resolveObjectResponse: ResolveObjectResponse;
}>;
type FilterType = "creator" | "community";
loading: boolean;
}) => {
return (
- <div className="form-group col-sm-6">
- <label className="col-form-label" htmlFor={`${filterType}-filter`}>
- {capitalizeFirstLetter(i18n.t(filterType))}
+ <div className="col-sm-6">
+ <label className="mb-1" htmlFor={`${filterType}-filter`}>
+ {capitalizeFirstLetter(I18NextService.i18n.t(filterType))}
</label>
<SearchableSelect
id={`${filterType}-filter`}
options={[
{
- label: i18n.t("all"),
+ label: I18NextService.i18n.t("all"),
value: "0",
},
].concat(options)}
return (
<>
<span>{listing}</span>
- <span>{` - ${i18n.t(translationKey, {
+ <span>{` - ${I18NextService.i18n.t(translationKey, {
count: Number(count),
formattedCount: numToSI(count),
})}`}</span>
query: { communityId, creatorId, q, type, sort, listingType, page },
}: InitialFetchRequest<QueryParams<SearchProps>>): Promise<SearchData> {
const community_id = getIdFromString(communityId);
- let communityResponse: RequestState<GetCommunityResponse> | undefined =
- undefined;
- let listCommunitiesResponse:
- | RequestState<ListCommunitiesResponse>
- | undefined = undefined;
+ let communityResponse: RequestState<GetCommunityResponse> = {
+ state: "empty",
+ };
+ let listCommunitiesResponse: RequestState<ListCommunitiesResponse> = {
+ state: "empty",
+ };
if (community_id) {
const getCommunityForm: GetCommunity = {
id: community_id,
}
const creator_id = getIdFromString(creatorId);
- let creatorDetailsResponse:
- | RequestState<GetPersonDetailsResponse>
- | undefined = undefined;
+ let creatorDetailsResponse: RequestState<GetPersonDetailsResponse> = {
+ state: "empty",
+ };
if (creator_id) {
const getCreatorForm: GetPersonDetails = {
person_id: creator_id,
const query = getSearchQueryFromQuery(q);
- let searchResponse: RequestState<SearchResponse> | undefined = undefined;
- let resolveObjectResponse: RequestState<ResolveObjectResponse> | undefined =
- undefined;
+ let searchResponse: RequestState<SearchResponse> = { state: "empty" };
+ let resolveObjectResponse: RequestState<ResolveObjectResponse> = {
+ state: "empty",
+ };
if (query) {
const form: SearchForm = {
q: query,
auth,
};
- resolveObjectResponse = await client
- .resolveObject(resolveObjectForm)
- .catch(() => undefined);
+ resolveObjectResponse = await HttpService.silent_client.resolveObject(
+ resolveObjectForm
+ );
+
+ // If we return this object with a state of failed, the catch-all-handler will redirect
+ // to an error page, so we ignore it by covering up the error with the empty state.
+ if (resolveObjectResponse.state === "failed") {
+ resolveObjectResponse = { state: "empty" };
+ }
}
}
}
get documentTitle(): string {
const { q } = getSearchQueryParams();
const name = this.state.siteRes.site_view.site.name;
- return `${i18n.t("search")} - ${q ? `${q} - ` : ""}${name}`;
+ return `${I18NextService.i18n.t("search")} - ${q ? `${q} - ` : ""}${name}`;
}
render() {
const { type, page } = getSearchQueryParams();
return (
- <div className="container-lg">
+ <div className="search container-lg">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ canonicalPath={
+ this.context.router.route.match.url +
+ this.context.router.route.location.search
+ }
/>
- <h5>{i18n.t("search")}</h5>
+ <h1 className="h4 mb-4">{I18NextService.i18n.t("search")}</h1>
{this.selects}
{this.searchForm}
{this.displayResults(type)}
{this.resultsCount === 0 &&
this.state.searchRes.state === "success" && (
- <span>{i18n.t("no_results")}</span>
+ <span>{I18NextService.i18n.t("no_results")}</span>
)}
<Paginator page={page} onChange={this.handlePageChange} />
</div>
get searchForm() {
return (
<form
- className="form-inline"
+ className="row gx-2 gy-3"
onSubmit={linkEvent(this, this.handleSearchSubmit)}
>
- <input
- type="text"
- className="form-control mr-2 mb-2"
- value={this.state.searchText}
- placeholder={`${i18n.t("search")}...`}
- aria-label={i18n.t("search")}
- onInput={linkEvent(this, this.handleQChange)}
- required
- minLength={1}
- />
- <button type="submit" className="btn btn-secondary mr-2 mb-2">
- {this.state.searchRes.state === "loading" ? (
- <Spinner />
- ) : (
- <span>{i18n.t("search")}</span>
- )}
- </button>
+ <div className="col-auto flex-grow-1 flex-sm-grow-0">
+ <input
+ type="text"
+ className="form-control me-2 mb-2 col-sm-8"
+ value={this.state.searchText}
+ placeholder={`${I18NextService.i18n.t("search")}...`}
+ aria-label={I18NextService.i18n.t("search")}
+ onInput={linkEvent(this, this.handleQChange)}
+ required
+ minLength={1}
+ />
+ </div>
+ <div className="col-auto">
+ <button type="submit" className="btn btn-secondary mb-2">
+ {this.state.searchRes.state === "loading" ? (
+ <Spinner />
+ ) : (
+ <span>{I18NextService.i18n.t("search")}</span>
+ )}
+ </button>
+ </div>
</form>
);
}
communitiesRes.data.communities.length > 0;
return (
- <div className="mb-2">
- <select
- value={type}
- onChange={linkEvent(this, this.handleTypeChange)}
- className="custom-select w-auto mb-2"
- aria-label={i18n.t("type")}
- >
- <option disabled aria-hidden="true">
- {i18n.t("type")}
- </option>
- {searchTypes.map(option => (
- <option value={option} key={option}>
- {i18n.t(option.toString().toLowerCase() as NoOptionI18nKeys)}
- </option>
- ))}
- </select>
- <span className="ml-2">
- <ListingTypeSelect
- type_={listingType}
- showLocal={showLocal(this.isoData)}
- showSubscribed
- onChange={this.handleListingTypeChange}
- />
- </span>
- <span className="ml-2">
- <SortSelect
- sort={sort}
- onChange={this.handleSortChange}
- hideHot
- hideMostComments
- />
- </span>
- <div className="form-row">
+ <>
+ <div className="row row-cols-auto g-2 g-sm-3 mb-2 mb-sm-3">
+ <div className="col">
+ <select
+ value={type}
+ onChange={linkEvent(this, this.handleTypeChange)}
+ className="form-select d-inline-block w-auto"
+ aria-label={I18NextService.i18n.t("type")}
+ >
+ <option disabled aria-hidden="true">
+ {I18NextService.i18n.t("type")}
+ </option>
+ {searchTypes.map(option => (
+ <option value={option} key={option}>
+ {I18NextService.i18n.t(
+ option.toString().toLowerCase() as NoOptionI18nKeys
+ )}
+ </option>
+ ))}
+ </select>
+ </div>
+ <div className="col">
+ <ListingTypeSelect
+ type_={listingType}
+ showLocal={showLocal(this.isoData)}
+ showSubscribed
+ onChange={this.handleListingTypeChange}
+ />
+ </div>
+ <div className="col">
+ <SortSelect
+ sort={sort}
+ onChange={this.handleSortChange}
+ hideHot
+ hideMostComments
+ />
+ </div>
+ </div>
+ <div className="row gy-2 gx-4 mb-3">
{hasCommunities && (
<Filter
filterType="community"
loading={searchCreatorLoading}
/>
</div>
- </div>
+ </>
);
}
if (auth) {
this.setState({ resolveObjectRes: { state: "loading" } });
this.setState({
- resolveObjectRes: await HttpService.client.resolveObject({
+ resolveObjectRes: await HttpService.silent_client.resolveObject({
q,
auth,
}),
};
this.props.history.push(`/search${getQueryString(queryParams)}`);
-
- await this.search();
}
}