-import { None, Option, Some } from "@sniptt/monads";
+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 {
- CommentResponse,
CommentView,
CommunityView,
GetCommunity,
ListCommunities,
ListCommunitiesResponse,
ListingType,
- PersonViewSafe,
- PostResponse,
+ PersonView,
PostView,
ResolveObject,
ResolveObjectResponse,
SearchResponse,
SearchType,
SortType,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
-import { i18n } from "../i18next";
+import { fetchLimit } from "../config";
import { CommentViewType, InitialFetchRequest } from "../interfaces";
-import { WebSocketService } from "../services";
-import {
- auth,
- capitalizeFirstLetter,
- choicesConfig,
- commentsToFlatNodes,
- communitySelectName,
- communityToChoice,
- createCommentLikeRes,
- createPostLikeFindRes,
- debounce,
- enableDownvotes,
- enableNsfw,
- fetchCommunities,
- fetchLimit,
- fetchUsers,
- isBrowser,
- numToSI,
- personSelectName,
- personToChoice,
- pushNotNull,
- restoreScrollPosition,
- routeListingTypeToEnum,
- routeSearchTypeToEnum,
- routeSortTypeToEnum,
- saveScrollPosition,
- setIsoData,
- showLocal,
- toast,
- wsClient,
- wsSubscribe,
-} from "../utils";
+import { FirstLoadService, I18NextService } from "../services";
+import { HttpService, RequestState } from "../services/HttpService";
import { CommentNodes } from "./comment/comment-nodes";
import { HtmlTags } from "./common/html-tags";
import { Spinner } from "./common/icon";
import { ListingTypeSelect } from "./common/listing-type-select";
import { Paginator } from "./common/paginator";
+import { SearchableSelect } from "./common/searchable-select";
import { SortSelect } from "./common/sort-select";
import { CommunityLink } from "./community/community-link";
import { PersonListing } from "./person/person-listing";
import { PostListing } from "./post/post-listing";
-var Choices: any;
-if (isBrowser()) {
- Choices = require("choices.js");
-}
-
interface SearchProps {
- q: string;
- type_: SearchType;
+ q?: string;
+ type: SearchType;
sort: SortType;
listingType: ListingType;
- communityId: number;
- creatorId: number;
+ communityId?: number | null;
+ creatorId?: number | null;
page: number;
}
+type SearchData = RouteDataResponse<{
+ communityResponse: GetCommunityResponse;
+ listCommunitiesResponse: ListCommunitiesResponse;
+ creatorDetailsResponse: GetPersonDetailsResponse;
+ searchResponse: SearchResponse;
+ resolveObjectResponse: ResolveObjectResponse;
+}>;
+
+type FilterType = "creator" | "community";
+
interface SearchState {
- q: string;
- type_: SearchType;
- sort: SortType;
- listingType: ListingType;
- communityId: number;
- creatorId: number;
- page: number;
- searchResponse: Option<SearchResponse>;
- communities: CommunityView[];
- creatorDetails: Option<GetPersonDetailsResponse>;
- loading: boolean;
+ searchRes: RequestState<SearchResponse>;
+ resolveObjectRes: RequestState<ResolveObjectResponse>;
+ creatorDetailsRes: RequestState<GetPersonDetailsResponse>;
+ communitiesRes: RequestState<ListCommunitiesResponse>;
+ communityRes: RequestState<GetCommunityResponse>;
siteRes: GetSiteResponse;
- searchText: string;
- resolveObjectResponse: Option<ResolveObjectResponse>;
-}
-
-interface UrlParams {
- q?: string;
- type_?: SearchType;
- sort?: SortType;
- listingType?: ListingType;
- communityId?: number;
- creatorId?: number;
- page?: number;
+ searchText?: string;
+ communitySearchOptions: Choice[];
+ creatorSearchOptions: Choice[];
+ searchCreatorLoading: boolean;
+ searchCommunitiesLoading: boolean;
+ isIsomorphic: boolean;
}
interface Combined {
type_: string;
- data: CommentView | PostView | CommunityView | PersonViewSafe;
+ data: CommentView | PostView | CommunityView | PersonView;
published: string;
}
-export class Search extends Component<any, SearchState> {
- private isoData = setIsoData(
- this.context,
- GetCommunityResponse,
- ListCommunitiesResponse,
- GetPersonDetailsResponse,
- SearchResponse,
- ResolveObjectResponse
- );
- private communityChoices: any;
- private creatorChoices: any;
- private subscription: Subscription;
- private emptyState: SearchState = {
- q: Search.getSearchQueryFromProps(this.props.match.params.q),
- type_: Search.getSearchTypeFromProps(this.props.match.params.type),
- sort: Search.getSortTypeFromProps(this.props.match.params.sort),
- listingType: Search.getListingTypeFromProps(
- this.props.match.params.listing_type
- ),
- page: Search.getPageFromProps(this.props.match.params.page),
- searchText: Search.getSearchQueryFromProps(this.props.match.params.q),
- communityId: Search.getCommunityIdFromProps(
- this.props.match.params.community_id
- ),
- creatorId: Search.getCreatorIdFromProps(this.props.match.params.creator_id),
- searchResponse: None,
- resolveObjectResponse: None,
- creatorDetails: None,
- loading: true,
- siteRes: this.isoData.site_res,
- communities: [],
+const defaultSearchType = "All";
+const defaultSortType = "TopAll";
+const defaultListingType = "All";
+
+const searchTypes = ["All", "Comments", "Posts", "Communities", "Users", "Url"];
+
+const getSearchQueryParams = () =>
+ getQueryParams<SearchProps>({
+ q: getSearchQueryFromQuery,
+ type: getSearchTypeFromQuery,
+ sort: getSortTypeFromQuery,
+ listingType: getListingTypeFromQuery,
+ communityId: getIdFromString,
+ creatorId: getIdFromString,
+ page: getPageFromString,
+ });
+
+const getSearchQueryFromQuery = (q?: string): string | undefined =>
+ q ? decodeURIComponent(q) : undefined;
+
+function getSearchTypeFromQuery(type_?: string): SearchType {
+ return type_ ? (type_ as SearchType) : defaultSearchType;
+}
+
+function getSortTypeFromQuery(sort?: string): SortType {
+ return sort ? (sort as SortType) : defaultSortType;
+}
+
+function getListingTypeFromQuery(listingType?: string): ListingType {
+ return listingType ? (listingType as ListingType) : defaultListingType;
+}
+
+function postViewToCombined(data: PostView): Combined {
+ return {
+ type_: "posts",
+ data,
+ published: data.post.published,
};
+}
- static getSearchQueryFromProps(q: string): string {
- return decodeURIComponent(q) || "";
- }
+function commentViewToCombined(data: CommentView): Combined {
+ return {
+ type_: "comments",
+ data,
+ published: data.comment.published,
+ };
+}
- static getSearchTypeFromProps(type_: string): SearchType {
- return type_ ? routeSearchTypeToEnum(type_) : SearchType.All;
- }
+function communityViewToCombined(data: CommunityView): Combined {
+ return {
+ type_: "communities",
+ data,
+ published: data.community.published,
+ };
+}
- static getSortTypeFromProps(sort: string): SortType {
- return sort ? routeSortTypeToEnum(sort) : SortType.TopAll;
- }
+function personViewSafeToCombined(data: PersonView): Combined {
+ return {
+ type_: "users",
+ data,
+ published: data.person.published,
+ };
+}
- static getListingTypeFromProps(listingType: string): ListingType {
- return listingType ? routeListingTypeToEnum(listingType) : ListingType.All;
- }
+const Filter = ({
+ filterType,
+ options,
+ onChange,
+ onSearch,
+ value,
+ loading,
+}: {
+ filterType: FilterType;
+ options: Choice[];
+ onSearch: (text: string) => void;
+ onChange: (choice: Choice) => void;
+ value?: number | null;
+ loading: boolean;
+}) => {
+ return (
+ <div className="col-sm-6">
+ <label className="mb-1" htmlFor={`${filterType}-filter`}>
+ {capitalizeFirstLetter(I18NextService.i18n.t(filterType))}
+ </label>
+ <SearchableSelect
+ id={`${filterType}-filter`}
+ options={[
+ {
+ label: I18NextService.i18n.t("all"),
+ value: "0",
+ },
+ ].concat(options)}
+ value={value ?? 0}
+ onSearch={onSearch}
+ onChange={onChange}
+ loading={loading}
+ />
+ </div>
+ );
+};
+
+const communityListing = ({
+ community,
+ counts: { subscribers },
+}: CommunityView) =>
+ getListing(
+ <CommunityLink community={community} />,
+ subscribers,
+ "number_of_subscribers"
+ );
- static getCommunityIdFromProps(id: string): number {
- return id ? Number(id) : 0;
- }
+const personListing = ({ person, counts: { comment_count } }: PersonView) =>
+ getListing(
+ <PersonListing person={person} showApubName />,
+ comment_count,
+ "number_of_comments"
+ );
- static getCreatorIdFromProps(id: string): number {
- return id ? Number(id) : 0;
- }
+function getListing(
+ listing: JSX.ElementClass,
+ count: number,
+ translationKey: "number_of_comments" | "number_of_subscribers"
+) {
+ return (
+ <>
+ <span>{listing}</span>
+ <span>{` - ${I18NextService.i18n.t(translationKey, {
+ count: Number(count),
+ formattedCount: numToSI(count),
+ })}`}</span>
+ </>
+ );
+}
- static getPageFromProps(page: string): number {
- return page ? Number(page) : 1;
- }
+export class Search extends Component<any, SearchState> {
+ private isoData = setIsoData<SearchData>(this.context);
+
+ state: SearchState = {
+ resolveObjectRes: { state: "empty" },
+ creatorDetailsRes: { state: "empty" },
+ communitiesRes: { state: "empty" },
+ communityRes: { state: "empty" },
+ siteRes: this.isoData.site_res,
+ creatorSearchOptions: [],
+ communitySearchOptions: [],
+ searchRes: { state: "empty" },
+ searchCreatorLoading: false,
+ searchCommunitiesLoading: false,
+ isIsomorphic: false,
+ };
constructor(props: any, context: any) {
super(props, context);
- this.state = this.emptyState;
this.handleSortChange = this.handleSortChange.bind(this);
this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
+ this.handleCommunityFilterChange =
+ this.handleCommunityFilterChange.bind(this);
+ this.handleCreatorFilterChange = this.handleCreatorFilterChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+ const { q } = getSearchQueryParams();
+
+ this.state = {
+ ...this.state,
+ searchText: q,
+ };
// Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
- let communityRes = Some(
- this.isoData.routeData[0] as GetCommunityResponse
- );
- let communitiesRes = Some(
- this.isoData.routeData[1] as ListCommunitiesResponse
- );
+ if (FirstLoadService.isFirstLoad) {
+ const {
+ communityResponse: communityRes,
+ creatorDetailsResponse: creatorDetailsRes,
+ listCommunitiesResponse: communitiesRes,
+ resolveObjectResponse: resolveObjectRes,
+ searchResponse: searchRes,
+ } = this.isoData.routeData;
- // This can be single or multiple communities given
- if (communitiesRes.isSome()) {
+ this.state = {
+ ...this.state,
+ isIsomorphic: true,
+ };
+
+ if (creatorDetailsRes?.state === "success") {
this.state = {
...this.state,
- communities: communitiesRes.unwrap().communities,
+ creatorSearchOptions:
+ creatorDetailsRes?.state === "success"
+ ? [personToChoice(creatorDetailsRes.data.person_view)]
+ : [],
+ creatorDetailsRes,
};
}
- if (communityRes.isSome()) {
+ if (communitiesRes?.state === "success") {
this.state = {
...this.state,
- communities: [communityRes.unwrap().community_view],
+ communitiesRes,
};
}
- this.state = {
- ...this.state,
- creatorDetails: Some(
- this.isoData.routeData[2] as GetPersonDetailsResponse
- ),
- };
+ if (communityRes?.state === "success") {
+ this.state = {
+ ...this.state,
+ communityRes,
+ };
+ }
- if (this.state.q != "") {
+ if (q !== "") {
this.state = {
...this.state,
- searchResponse: Some(this.isoData.routeData[3] as SearchResponse),
- resolveObjectResponse: Some(
- this.isoData.routeData[4] as ResolveObjectResponse
- ),
- loading: false,
};
- } else {
- this.search();
+
+ if (searchRes?.state === "success") {
+ this.state = {
+ ...this.state,
+ searchRes,
+ };
+ }
+
+ if (resolveObjectRes?.state === "success") {
+ this.state = {
+ ...this.state,
+ resolveObjectRes,
+ };
+ }
}
- } else {
- this.fetchCommunities();
- this.search();
}
}
- componentWillUnmount() {
- this.subscription.unsubscribe();
- saveScrollPosition(this.context);
- }
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ const promises = [this.fetchCommunities()];
+ if (this.state.searchText) {
+ promises.push(this.search());
+ }
- componentDidMount() {
- this.setupCommunityFilter();
- this.setupCreatorFilter();
+ await Promise.all(promises);
+ }
}
- static getDerivedStateFromProps(props: any): SearchProps {
- return {
- q: Search.getSearchQueryFromProps(props.match.params.q),
- type_: Search.getSearchTypeFromProps(props.match.params.type),
- sort: Search.getSortTypeFromProps(props.match.params.sort),
- listingType: Search.getListingTypeFromProps(
- props.match.params.listing_type
- ),
- communityId: Search.getCommunityIdFromProps(
- props.match.params.community_id
- ),
- creatorId: Search.getCreatorIdFromProps(props.match.params.creator_id),
- page: Search.getPageFromProps(props.match.params.page),
- };
+ async fetchCommunities() {
+ this.setState({ communitiesRes: { state: "loading" } });
+ this.setState({
+ communitiesRes: await HttpService.client.listCommunities({
+ type_: defaultListingType,
+ sort: defaultSortType,
+ limit: fetchLimit,
+ auth: myAuth(),
+ }),
+ });
}
- fetchCommunities() {
- let listCommunitiesForm = new ListCommunities({
- type_: Some(ListingType.All),
- sort: Some(SortType.TopAll),
- limit: Some(fetchLimit),
- page: None,
- auth: auth(false).ok(),
- });
- WebSocketService.Instance.send(
- wsClient.listCommunities(listCommunitiesForm)
- );
+ componentWillUnmount() {
+ saveScrollPosition(this.context);
}
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- let pathSplit = req.path.split("/");
- let promises: Promise<any>[] = [];
-
- let communityId = this.getCommunityIdFromProps(pathSplit[11]);
- let community_id: Option<number> =
- communityId == 0 ? None : Some(communityId);
- community_id.match({
- some: id => {
- let getCommunityForm = new GetCommunity({
- id: Some(id),
- name: None,
- auth: req.auth,
- });
- promises.push(req.client.getCommunity(getCommunityForm));
- promises.push(Promise.resolve());
- },
- none: () => {
- let listCommunitiesForm = new ListCommunities({
- type_: Some(ListingType.All),
- sort: Some(SortType.TopAll),
- limit: Some(fetchLimit),
- page: None,
- auth: req.auth,
- });
- promises.push(Promise.resolve());
- promises.push(req.client.listCommunities(listCommunitiesForm));
- },
- });
+ static async fetchInitialData({
+ client,
+ auth,
+ query: { communityId, creatorId, q, type, sort, listingType, page },
+ }: InitialFetchRequest<QueryParams<SearchProps>>): Promise<SearchData> {
+ const community_id = getIdFromString(communityId);
+ let communityResponse: RequestState<GetCommunityResponse> = {
+ state: "empty",
+ };
+ let listCommunitiesResponse: RequestState<ListCommunitiesResponse> = {
+ state: "empty",
+ };
+ if (community_id) {
+ const getCommunityForm: GetCommunity = {
+ id: community_id,
+ auth,
+ };
- let creatorId = this.getCreatorIdFromProps(pathSplit[13]);
- let creator_id: Option<number> = creatorId == 0 ? None : Some(creatorId);
- creator_id.match({
- some: id => {
- let getCreatorForm = new GetPersonDetails({
- person_id: Some(id),
- username: None,
- sort: None,
- page: None,
- limit: None,
- community_id: None,
- saved_only: None,
- auth: req.auth,
- });
- promises.push(req.client.getPersonDetails(getCreatorForm));
- },
- none: () => {
- promises.push(Promise.resolve());
- },
- });
+ communityResponse = await client.getCommunity(getCommunityForm);
+ } else {
+ const listCommunitiesForm: ListCommunities = {
+ type_: defaultListingType,
+ sort: defaultSortType,
+ limit: fetchLimit,
+ auth,
+ };
- let form = new SearchForm({
- q: this.getSearchQueryFromProps(pathSplit[3]),
- community_id,
- community_name: None,
- creator_id,
- type_: Some(this.getSearchTypeFromProps(pathSplit[5])),
- sort: Some(this.getSortTypeFromProps(pathSplit[7])),
- listing_type: Some(this.getListingTypeFromProps(pathSplit[9])),
- page: Some(this.getPageFromProps(pathSplit[15])),
- limit: Some(fetchLimit),
- auth: req.auth,
- });
+ listCommunitiesResponse = await client.listCommunities(
+ listCommunitiesForm
+ );
+ }
- let resolveObjectForm = new ResolveObject({
- q: this.getSearchQueryFromProps(pathSplit[3]),
- auth: req.auth,
- });
+ const creator_id = getIdFromString(creatorId);
+ let creatorDetailsResponse: RequestState<GetPersonDetailsResponse> = {
+ state: "empty",
+ };
+ if (creator_id) {
+ const getCreatorForm: GetPersonDetails = {
+ person_id: creator_id,
+ auth,
+ };
- if (form.q != "") {
- promises.push(req.client.search(form));
- promises.push(req.client.resolveObject(resolveObjectForm));
- } else {
- promises.push(Promise.resolve());
- promises.push(Promise.resolve());
+ creatorDetailsResponse = await client.getPersonDetails(getCreatorForm);
}
- return promises;
- }
+ const query = getSearchQueryFromQuery(q);
- componentDidUpdate(_: any, lastState: SearchState) {
- if (
- lastState.q !== this.state.q ||
- lastState.type_ !== this.state.type_ ||
- lastState.sort !== this.state.sort ||
- lastState.listingType !== this.state.listingType ||
- lastState.communityId !== this.state.communityId ||
- lastState.creatorId !== this.state.creatorId ||
- lastState.page !== this.state.page
- ) {
- this.setState({
- loading: true,
- searchText: this.state.q,
- searchResponse: None,
- resolveObjectResponse: None,
- });
- this.search();
+ let searchResponse: RequestState<SearchResponse> = { state: "empty" };
+ let resolveObjectResponse: RequestState<ResolveObjectResponse> = {
+ state: "empty",
+ };
+
+ if (query) {
+ const form: SearchForm = {
+ q: query,
+ community_id,
+ creator_id,
+ type_: getSearchTypeFromQuery(type),
+ sort: getSortTypeFromQuery(sort),
+ listing_type: getListingTypeFromQuery(listingType),
+ page: getPageFromString(page),
+ limit: fetchLimit,
+ auth,
+ };
+
+ if (query !== "") {
+ searchResponse = await client.search(form);
+ if (auth) {
+ const resolveObjectForm: ResolveObject = {
+ q: query,
+ auth,
+ };
+ 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" };
+ }
+ }
+ }
}
+
+ return {
+ communityResponse,
+ creatorDetailsResponse,
+ listCommunitiesResponse,
+ resolveObjectResponse,
+ searchResponse,
+ };
}
get documentTitle(): string {
- return this.state.siteRes.site_view.match({
- some: siteView =>
- this.state.q
- ? `${i18n.t("search")} - ${this.state.q} - ${siteView.site.name}`
- : `${i18n.t("search")} - ${siteView.site.name}`,
- none: "",
- });
+ const { q } = getSearchQueryParams();
+ const name = this.state.siteRes.site_view.site.name;
+ return `${I18NextService.i18n.t("search")} - ${q ? `${q} - ` : ""}${name}`;
}
render() {
+ const { type, page } = getSearchQueryParams();
+
return (
- <div className="container">
+ <div className="search container-lg">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
- description={None}
- image={None}
+ canonicalPath={
+ this.context.router.route.match.url +
+ this.context.router.route.location.search
+ }
/>
- <h5>{i18n.t("search")}</h5>
- {this.selects()}
- {this.searchForm()}
- {this.state.type_ == SearchType.All && this.all()}
- {this.state.type_ == SearchType.Comments && this.comments()}
- {this.state.type_ == SearchType.Posts && this.posts()}
- {this.state.type_ == SearchType.Communities && this.communities()}
- {this.state.type_ == SearchType.Users && this.users()}
- {this.state.type_ == SearchType.Url && this.posts()}
- {this.resultsCount() == 0 && <span>{i18n.t("no_results")}</span>}
- <Paginator page={this.state.page} onChange={this.handlePageChange} />
+ <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>{I18NextService.i18n.t("no_results")}</span>
+ )}
+ <Paginator page={page} onChange={this.handlePageChange} />
</div>
);
}
- searchForm() {
+ displayResults(type: SearchType) {
+ switch (type) {
+ case "All":
+ return this.all;
+ case "Comments":
+ return this.comments;
+ case "Posts":
+ case "Url":
+ return this.posts;
+ case "Communities":
+ return this.communities;
+ case "Users":
+ return this.users;
+ default:
+ return <></>;
+ }
+ }
+
+ 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.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>
);
}
- selects() {
+ get selects() {
+ const { type, listingType, sort, communityId, creatorId } =
+ getSearchQueryParams();
+ const {
+ communitySearchOptions,
+ creatorSearchOptions,
+ searchCommunitiesLoading,
+ searchCreatorLoading,
+ communitiesRes,
+ } = this.state;
+
+ const hasCommunities =
+ communitiesRes.state == "success" &&
+ communitiesRes.data.communities.length > 0;
+
return (
- <div className="mb-2">
- <select
- value={this.state.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>
- <option value={SearchType.All}>{i18n.t("all")}</option>
- <option value={SearchType.Comments}>{i18n.t("comments")}</option>
- <option value={SearchType.Posts}>{i18n.t("posts")}</option>
- <option value={SearchType.Communities}>
- {i18n.t("communities")}
- </option>
- <option value={SearchType.Users}>{i18n.t("users")}</option>
- <option value={SearchType.Url}>{i18n.t("url")}</option>
- </select>
- <span className="ml-2">
- <ListingTypeSelect
- type_={this.state.listingType}
- showLocal={showLocal(this.isoData)}
- showSubscribed
- onChange={this.handleListingTypeChange}
- />
- </span>
- <span className="ml-2">
- <SortSelect
- sort={this.state.sort}
- onChange={this.handleSortChange}
- hideHot
- hideMostComments
+ <>
+ <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"
+ onChange={this.handleCommunityFilterChange}
+ onSearch={this.handleCommunitySearch}
+ options={communitySearchOptions}
+ value={communityId}
+ loading={searchCommunitiesLoading}
+ />
+ )}
+ <Filter
+ filterType="creator"
+ onChange={this.handleCreatorFilterChange}
+ onSearch={this.handleCreatorSearch}
+ options={creatorSearchOptions}
+ value={creatorId}
+ loading={searchCreatorLoading}
/>
- </span>
- <div className="form-row">
- {this.state.communities.length > 0 && this.communityFilter()}
- {this.creatorFilter()}
</div>
- </div>
+ </>
);
}
- postViewToCombined(postView: PostView): Combined {
- return {
- type_: "posts",
- data: postView,
- published: postView.post.published,
- };
- }
-
- commentViewToCombined(commentView: CommentView): Combined {
- return {
- type_: "comments",
- data: commentView,
- published: commentView.comment.published,
- };
- }
-
- communityViewToCombined(communityView: CommunityView): Combined {
- return {
- type_: "communities",
- data: communityView,
- published: communityView.community.published,
- };
- }
-
- personViewSafeToCombined(personViewSafe: PersonViewSafe): Combined {
- return {
- type_: "users",
- data: personViewSafe,
- published: personViewSafe.person.published,
- };
- }
-
buildCombined(): Combined[] {
- let combined: Combined[] = [];
+ const combined: Combined[] = [];
+ const {
+ resolveObjectRes: resolveObjectResponse,
+ searchRes: searchResponse,
+ } = this.state;
// Push the possible resolve / federated objects first
- this.state.resolveObjectResponse.match({
- some: res => {
- let resolveComment = res.comment;
- if (resolveComment.isSome()) {
- combined.push(this.commentViewToCombined(resolveComment.unwrap()));
- }
- let resolvePost = res.post;
- if (resolvePost.isSome()) {
- combined.push(this.postViewToCombined(resolvePost.unwrap()));
- }
- let resolveCommunity = res.community;
- if (resolveCommunity.isSome()) {
- combined.push(
- this.communityViewToCombined(resolveCommunity.unwrap())
- );
- }
- let resolveUser = res.person;
- if (resolveUser.isSome()) {
- combined.push(this.personViewSafeToCombined(resolveUser.unwrap()));
- }
- },
- none: void 0,
- });
+ if (resolveObjectResponse.state == "success") {
+ const { comment, post, community, person } = resolveObjectResponse.data;
+
+ if (comment) {
+ combined.push(commentViewToCombined(comment));
+ }
+ if (post) {
+ combined.push(postViewToCombined(post));
+ }
+ if (community) {
+ combined.push(communityViewToCombined(community));
+ }
+ if (person) {
+ combined.push(personViewSafeToCombined(person));
+ }
+ }
// Push the search results
- this.state.searchResponse.match({
- some: res => {
- pushNotNull(
- combined,
- res.comments?.map(e => this.commentViewToCombined(e))
- );
- pushNotNull(
- combined,
- res.posts?.map(e => this.postViewToCombined(e))
- );
- pushNotNull(
- combined,
- res.communities?.map(e => this.communityViewToCombined(e))
- );
- pushNotNull(
- combined,
- res.users?.map(e => this.personViewSafeToCombined(e))
- );
- },
- none: void 0,
- });
+ if (searchResponse.state === "success") {
+ const { comments, posts, communities, users } = searchResponse.data;
+
+ combined.push(
+ ...[
+ ...(comments?.map(commentViewToCombined) ?? []),
+ ...(posts?.map(postViewToCombined) ?? []),
+ ...(communities?.map(communityViewToCombined) ?? []),
+ ...(users?.map(personViewSafeToCombined) ?? []),
+ ]
+ );
+ }
+
+ const { sort } = getSearchQueryParams();
// Sort it
- if (this.state.sort == SortType.New) {
+ if (sort === "New") {
combined.sort((a, b) => b.published.localeCompare(a.published));
} else {
- combined.sort(
- (a, b) =>
+ combined.sort((a, b) =>
+ Number(
((b.data as CommentView | PostView).counts.score |
(b.data as CommunityView).counts.subscribers |
- (b.data as PersonViewSafe).counts.comment_score) -
- ((a.data as CommentView | PostView).counts.score |
- (a.data as CommunityView).counts.subscribers |
- (a.data as PersonViewSafe).counts.comment_score)
+ (b.data as PersonView).counts.comment_score) -
+ ((a.data as CommentView | PostView).counts.score |
+ (a.data as CommunityView).counts.subscribers |
+ (a.data as PersonView).counts.comment_score)
+ )
);
}
+
return combined;
}
- all() {
- let combined = this.buildCombined();
+ get all() {
+ const combined = this.buildCombined();
+
return (
<div>
{combined.map(i => (
<div key={i.published} className="row">
<div className="col-12">
- {i.type_ == "posts" && (
+ {i.type_ === "posts" && (
<PostListing
key={(i.data as PostView).post.id}
post_view={i.data as PostView}
- duplicates={None}
- moderators={None}
- admins={None}
showCommunity
enableDownvotes={enableDownvotes(this.state.siteRes)}
enableNsfw={enableNsfw(this.state.siteRes)}
allLanguages={this.state.siteRes.all_languages}
+ siteLanguages={this.state.siteRes.discussion_languages}
viewOnly
+ // All of these are unused, since its view only
+ onPostEdit={() => {}}
+ onPostVote={() => {}}
+ onPostReport={() => {}}
+ onBlockPerson={() => {}}
+ onLockPost={() => {}}
+ onDeletePost={() => {}}
+ onRemovePost={() => {}}
+ onSavePost={() => {}}
+ onFeaturePost={() => {}}
+ onPurgePerson={() => {}}
+ onPurgePost={() => {}}
+ onBanPersonFromCommunity={() => {}}
+ onBanPerson={() => {}}
+ onAddModToCommunity={() => {}}
+ onAddAdmin={() => {}}
+ onTransferCommunity={() => {}}
/>
)}
- {i.type_ == "comments" && (
+ {i.type_ === "comments" && (
<CommentNodes
key={(i.data as CommentView).comment.id}
nodes={[
]}
viewType={CommentViewType.Flat}
viewOnly
- moderators={None}
- admins={None}
- maxCommentsShown={None}
locked
noIndent
enableDownvotes={enableDownvotes(this.state.siteRes)}
allLanguages={this.state.siteRes.all_languages}
+ siteLanguages={this.state.siteRes.discussion_languages}
+ // All of these are unused, since its viewonly
+ finished={new Map()}
+ onSaveComment={() => {}}
+ onBlockPerson={() => {}}
+ onDeleteComment={() => {}}
+ onRemoveComment={() => {}}
+ onCommentVote={() => {}}
+ onCommentReport={() => {}}
+ onDistinguishComment={() => {}}
+ onAddModToCommunity={() => {}}
+ onAddAdmin={() => {}}
+ onTransferCommunity={() => {}}
+ onPurgeComment={() => {}}
+ onPurgePerson={() => {}}
+ onCommentReplyRead={() => {}}
+ onPersonMentionRead={() => {}}
+ onBanPersonFromCommunity={() => {}}
+ onBanPerson={() => {}}
+ onCreateComment={() => Promise.resolve({ state: "empty" })}
+ onEditComment={() => Promise.resolve({ state: "empty" })}
/>
)}
- {i.type_ == "communities" && (
- <div>{this.communityListing(i.data as CommunityView)}</div>
+ {i.type_ === "communities" && (
+ <div>{communityListing(i.data as CommunityView)}</div>
)}
- {i.type_ == "users" && (
- <div>{this.personListing(i.data as PersonViewSafe)}</div>
+ {i.type_ === "users" && (
+ <div>{personListing(i.data as PersonView)}</div>
)}
</div>
</div>
);
}
- comments() {
- let comments: CommentView[] = [];
+ get comments() {
+ const {
+ searchRes: searchResponse,
+ resolveObjectRes: resolveObjectResponse,
+ siteRes,
+ } = this.state;
+ const comments =
+ searchResponse.state === "success" ? searchResponse.data.comments : [];
- this.state.resolveObjectResponse.match({
- some: res => pushNotNull(comments, res.comment),
- none: void 0,
- });
- this.state.searchResponse.match({
- some: res => pushNotNull(comments, res.comments),
- none: void 0,
- });
+ if (
+ resolveObjectResponse.state === "success" &&
+ resolveObjectResponse.data.comment
+ ) {
+ comments.unshift(resolveObjectResponse.data.comment);
+ }
return (
<CommentNodes
viewOnly
locked
noIndent
- moderators={None}
- admins={None}
- maxCommentsShown={None}
- enableDownvotes={enableDownvotes(this.state.siteRes)}
- allLanguages={this.state.siteRes.all_languages}
+ enableDownvotes={enableDownvotes(siteRes)}
+ allLanguages={siteRes.all_languages}
+ siteLanguages={siteRes.discussion_languages}
+ // All of these are unused, since its viewonly
+ finished={new Map()}
+ onSaveComment={() => {}}
+ onBlockPerson={() => {}}
+ onDeleteComment={() => {}}
+ onRemoveComment={() => {}}
+ onCommentVote={() => {}}
+ onCommentReport={() => {}}
+ onDistinguishComment={() => {}}
+ onAddModToCommunity={() => {}}
+ onAddAdmin={() => {}}
+ onTransferCommunity={() => {}}
+ onPurgeComment={() => {}}
+ onPurgePerson={() => {}}
+ onCommentReplyRead={() => {}}
+ onPersonMentionRead={() => {}}
+ onBanPersonFromCommunity={() => {}}
+ onBanPerson={() => {}}
+ onCreateComment={() => Promise.resolve({ state: "empty" })}
+ onEditComment={() => Promise.resolve({ state: "empty" })}
/>
);
}
- posts() {
- let posts: PostView[] = [];
+ get posts() {
+ const {
+ searchRes: searchResponse,
+ resolveObjectRes: resolveObjectResponse,
+ siteRes,
+ } = this.state;
+ const posts =
+ searchResponse.state === "success" ? searchResponse.data.posts : [];
- this.state.resolveObjectResponse.match({
- some: res => pushNotNull(posts, res.post),
- none: void 0,
- });
- this.state.searchResponse.match({
- some: res => pushNotNull(posts, res.posts),
- none: void 0,
- });
+ if (
+ resolveObjectResponse.state === "success" &&
+ resolveObjectResponse.data.post
+ ) {
+ posts.unshift(resolveObjectResponse.data.post);
+ }
return (
<>
<PostListing
post_view={pv}
showCommunity
- duplicates={None}
- moderators={None}
- admins={None}
- enableDownvotes={enableDownvotes(this.state.siteRes)}
- enableNsfw={enableNsfw(this.state.siteRes)}
- allLanguages={this.state.siteRes.all_languages}
+ enableDownvotes={enableDownvotes(siteRes)}
+ enableNsfw={enableNsfw(siteRes)}
+ allLanguages={siteRes.all_languages}
+ siteLanguages={siteRes.discussion_languages}
viewOnly
+ // All of these are unused, since its view only
+ onPostEdit={() => {}}
+ onPostVote={() => {}}
+ onPostReport={() => {}}
+ onBlockPerson={() => {}}
+ onLockPost={() => {}}
+ onDeletePost={() => {}}
+ onRemovePost={() => {}}
+ onSavePost={() => {}}
+ onFeaturePost={() => {}}
+ onPurgePerson={() => {}}
+ onPurgePost={() => {}}
+ onBanPersonFromCommunity={() => {}}
+ onBanPerson={() => {}}
+ onAddModToCommunity={() => {}}
+ onAddAdmin={() => {}}
+ onTransferCommunity={() => {}}
/>
</div>
</div>
);
}
- communities() {
- let communities: CommunityView[] = [];
+ get communities() {
+ const {
+ searchRes: searchResponse,
+ resolveObjectRes: resolveObjectResponse,
+ } = this.state;
+ const communities =
+ searchResponse.state === "success" ? searchResponse.data.communities : [];
- this.state.resolveObjectResponse.match({
- some: res => pushNotNull(communities, res.community),
- none: void 0,
- });
- this.state.searchResponse.match({
- some: res => pushNotNull(communities, res.communities),
- none: void 0,
- });
+ if (
+ resolveObjectResponse.state === "success" &&
+ resolveObjectResponse.data.community
+ ) {
+ communities.unshift(resolveObjectResponse.data.community);
+ }
return (
<>
{communities.map(cv => (
<div key={cv.community.id} className="row">
- <div className="col-12">{this.communityListing(cv)}</div>
+ <div className="col-12">{communityListing(cv)}</div>
</div>
))}
</>
);
}
- users() {
- let users: PersonViewSafe[] = [];
+ get users() {
+ const {
+ searchRes: searchResponse,
+ resolveObjectRes: resolveObjectResponse,
+ } = this.state;
+ const users =
+ searchResponse.state === "success" ? searchResponse.data.users : [];
- this.state.resolveObjectResponse.match({
- some: res => pushNotNull(users, res.person),
- none: void 0,
- });
- this.state.searchResponse.match({
- some: res => pushNotNull(users, res.users),
- none: void 0,
- });
+ if (
+ resolveObjectResponse.state === "success" &&
+ resolveObjectResponse.data.person
+ ) {
+ users.unshift(resolveObjectResponse.data.person);
+ }
return (
<>
{users.map(pvs => (
<div key={pvs.person.id} className="row">
- <div className="col-12">{this.personListing(pvs)}</div>
+ <div className="col-12">{personListing(pvs)}</div>
</div>
))}
</>
);
}
- communityListing(community_view: CommunityView) {
- return (
- <>
- <span>
- <CommunityLink community={community_view.community} />
- </span>
- <span>{` -
- ${i18n.t("number_of_subscribers", {
- count: community_view.counts.subscribers,
- formattedCount: numToSI(community_view.counts.subscribers),
- })}
- `}</span>
- </>
- );
- }
+ get resultsCount(): number {
+ const { searchRes: r, resolveObjectRes: resolveRes } = this.state;
+
+ const searchCount =
+ r.state === "success"
+ ? r.data.posts.length +
+ r.data.comments.length +
+ r.data.communities.length +
+ r.data.users.length
+ : 0;
+
+ const resObjCount =
+ resolveRes.state === "success"
+ ? resolveRes.data.post ||
+ resolveRes.data.person ||
+ resolveRes.data.community ||
+ resolveRes.data.comment
+ ? 1
+ : 0
+ : 0;
- personListing(person_view: PersonViewSafe) {
- return (
- <>
- <span>
- <PersonListing person={person_view.person} showApubName />
- </span>
- <span>{` - ${i18n.t("number_of_comments", {
- count: person_view.counts.comment_count,
- formattedCount: numToSI(person_view.counts.comment_count),
- })}`}</span>
- </>
- );
+ return resObjCount + searchCount;
}
- communityFilter() {
- return (
- <div className="form-group col-sm-6">
- <label className="col-form-label" htmlFor="community-filter">
- {i18n.t("community")}
- </label>
- <div>
- <select
- className="form-control"
- id="community-filter"
- value={this.state.communityId}
- >
- <option value="0">{i18n.t("all")}</option>
- {this.state.communities.map(cv => (
- <option key={cv.community.id} value={cv.community.id}>
- {communitySelectName(cv)}
- </option>
- ))}
- </select>
- </div>
- </div>
- );
- }
+ async search() {
+ const auth = myAuth();
+ const { searchText: q } = this.state;
+ const { communityId, creatorId, type, sort, listingType, page } =
+ getSearchQueryParams();
- creatorFilter() {
- return (
- <div className="form-group col-sm-6">
- <label className="col-form-label" htmlFor="creator-filter">
- {capitalizeFirstLetter(i18n.t("creator"))}
- </label>
- <div>
- <select
- className="form-control"
- id="creator-filter"
- value={this.state.creatorId}
- >
- <option value="0">{i18n.t("all")}</option>
- {this.state.creatorDetails.match({
- some: creator => (
- <option value={creator.person_view.person.id}>
- {personSelectName(creator.person_view)}
- </option>
- ),
- none: <></>,
- })}
- </select>
- </div>
- </div>
- );
+ if (q) {
+ this.setState({ searchRes: { state: "loading" } });
+ this.setState({
+ searchRes: await HttpService.client.search({
+ q,
+ community_id: communityId ?? undefined,
+ creator_id: creatorId ?? undefined,
+ type_: type,
+ sort,
+ listing_type: listingType,
+ page,
+ limit: fetchLimit,
+ auth,
+ }),
+ });
+ window.scrollTo(0, 0);
+ restoreScrollPosition(this.context);
+
+ if (auth) {
+ this.setState({ resolveObjectRes: { state: "loading" } });
+ this.setState({
+ resolveObjectRes: await HttpService.silent_client.resolveObject({
+ q,
+ auth,
+ }),
+ });
+ }
+ }
}
- resultsCount(): number {
- let searchCount = this.state.searchResponse
- .map(
- r =>
- r.posts?.length +
- r.comments?.length +
- r.communities?.length +
- r.users?.length
- )
- .unwrapOr(0);
-
- let resObjCount = this.state.resolveObjectResponse
- .map(r => (r.post || r.person || r.community || r.comment ? 1 : 0))
- .unwrapOr(0);
+ handleCreatorSearch = debounce(async (text: string) => {
+ const { creatorId } = getSearchQueryParams();
+ const { creatorSearchOptions } = this.state;
+ const newOptions: Choice[] = [];
- return resObjCount + searchCount;
- }
+ this.setState({ searchCreatorLoading: true });
- handlePageChange(page: number) {
- this.updateUrl({ page });
- }
+ const selectedChoice = creatorSearchOptions.find(
+ choice => getIdFromString(choice.value) === creatorId
+ );
+
+ if (selectedChoice) {
+ newOptions.push(selectedChoice);
+ }
+
+ if (text.length > 0) {
+ newOptions.push(...(await fetchUsers(text)).map(personToChoice));
+ }
- search() {
- let community_id: Option<number> =
- this.state.communityId == 0 ? None : Some(this.state.communityId);
- let creator_id: Option<number> =
- this.state.creatorId == 0 ? None : Some(this.state.creatorId);
-
- let form = new SearchForm({
- q: this.state.q,
- community_id,
- community_name: None,
- creator_id,
- type_: Some(this.state.type_),
- sort: Some(this.state.sort),
- listing_type: Some(this.state.listingType),
- page: Some(this.state.page),
- limit: Some(fetchLimit),
- auth: auth(false).ok(),
+ this.setState({
+ searchCreatorLoading: false,
+ creatorSearchOptions: newOptions,
});
+ });
- let resolveObjectForm = new ResolveObject({
- q: this.state.q,
- auth: auth(false).ok(),
+ handleCommunitySearch = debounce(async (text: string) => {
+ const { communityId } = getSearchQueryParams();
+ const { communitySearchOptions } = this.state;
+ this.setState({
+ searchCommunitiesLoading: true,
});
- if (this.state.q != "") {
- this.setState({
- searchResponse: None,
- resolveObjectResponse: None,
- loading: true,
- });
- WebSocketService.Instance.send(wsClient.search(form));
- WebSocketService.Instance.send(wsClient.resolveObject(resolveObjectForm));
- }
- }
+ const newOptions: Choice[] = [];
- setupCommunityFilter() {
- if (isBrowser()) {
- let selectId: any = document.getElementById("community-filter");
- if (selectId) {
- this.communityChoices = new Choices(selectId, choicesConfig);
- this.communityChoices.passedElement.element.addEventListener(
- "choice",
- (e: any) => {
- this.handleCommunityFilterChange(Number(e.detail.choice.value));
- },
- false
- );
- this.communityChoices.passedElement.element.addEventListener(
- "search",
- debounce(async (e: any) => {
- try {
- let communities = (await fetchCommunities(e.detail.value))
- .communities;
- let choices = communities.map(cv => communityToChoice(cv));
- choices.unshift({ value: "0", label: i18n.t("all") });
- this.communityChoices.setChoices(choices, "value", "label", true);
- } catch (err) {
- console.error(err);
- }
- }),
- false
- );
- }
+ const selectedChoice = communitySearchOptions.find(
+ choice => getIdFromString(choice.value) === communityId
+ );
+
+ if (selectedChoice) {
+ newOptions.push(selectedChoice);
}
- }
- setupCreatorFilter() {
- if (isBrowser()) {
- let selectId: any = document.getElementById("creator-filter");
- if (selectId) {
- this.creatorChoices = new Choices(selectId, choicesConfig);
- this.creatorChoices.passedElement.element.addEventListener(
- "choice",
- (e: any) => {
- this.handleCreatorFilterChange(Number(e.detail.choice.value));
- },
- false
- );
- this.creatorChoices.passedElement.element.addEventListener(
- "search",
- debounce(async (e: any) => {
- try {
- let creators = (await fetchUsers(e.detail.value)).users;
- let choices = creators.map(pvs => personToChoice(pvs));
- choices.unshift({ value: "0", label: i18n.t("all") });
- this.creatorChoices.setChoices(choices, "value", "label", true);
- } catch (err) {
- console.log(err);
- }
- }),
- false
- );
- }
+ if (text.length > 0) {
+ newOptions.push(...(await fetchCommunities(text)).map(communityToChoice));
}
- }
- handleSortChange(val: SortType) {
- this.updateUrl({ sort: val, page: 1 });
+ this.setState({
+ searchCommunitiesLoading: false,
+ communitySearchOptions: newOptions,
+ });
+ });
+
+ handleSortChange(sort: SortType) {
+ this.updateUrl({ sort, page: 1 });
}
handleTypeChange(i: Search, event: any) {
+ const type = event.target.value as SearchType;
+
i.updateUrl({
- type_: SearchType[event.target.value],
+ type,
page: 1,
});
}
- handleListingTypeChange(val: ListingType) {
+ handlePageChange(page: number) {
+ this.updateUrl({ page });
+ }
+
+ handleListingTypeChange(listingType: ListingType) {
this.updateUrl({
- listingType: val,
+ listingType,
page: 1,
});
}
- handleCommunityFilterChange(communityId: number) {
+ handleCommunityFilterChange({ value }: Choice) {
this.updateUrl({
- communityId,
+ communityId: getIdFromString(value) ?? null,
page: 1,
});
}
- handleCreatorFilterChange(creatorId: number) {
+ handleCreatorFilterChange({ value }: Choice) {
this.updateUrl({
- creatorId,
+ creatorId: getIdFromString(value) ?? null,
page: 1,
});
}
handleSearchSubmit(i: Search, event: any) {
event.preventDefault();
+
i.updateUrl({
q: i.state.searchText,
- type_: i.state.type_,
- listingType: i.state.listingType,
- communityId: i.state.communityId,
- creatorId: i.state.creatorId,
- sort: i.state.sort,
- page: i.state.page,
+ page: 1,
});
}
i.setState({ searchText: event.target.value });
}
- updateUrl(paramUpdates: UrlParams) {
- const qStr = paramUpdates.q || this.state.q;
- const qStrEncoded = encodeURIComponent(qStr);
- const typeStr = paramUpdates.type_ || this.state.type_;
- const listingTypeStr = paramUpdates.listingType || this.state.listingType;
- const sortStr = paramUpdates.sort || this.state.sort;
- const communityId =
- paramUpdates.communityId == 0
- ? 0
- : paramUpdates.communityId || this.state.communityId;
- const creatorId =
- paramUpdates.creatorId == 0
- ? 0
- : paramUpdates.creatorId || this.state.creatorId;
- const page = paramUpdates.page || this.state.page;
- this.props.history.push(
- `/search/q/${qStrEncoded}/type/${typeStr}/sort/${sortStr}/listing_type/${listingTypeStr}/community_id/${communityId}/creator_id/${creatorId}/page/${page}`
- );
- }
-
- parseMessage(msg: any) {
- console.log(msg);
- let op = wsUserOp(msg);
- if (msg.error) {
- if (msg.error == "couldnt_find_object") {
- this.setState({
- resolveObjectResponse: Some({
- comment: None,
- post: None,
- community: None,
- person: None,
- }),
- });
- this.checkFinishedLoading();
- } else {
- toast(i18n.t(msg.error), "danger");
- return;
- }
- } else if (op == UserOperation.Search) {
- let data = wsJsonToRes<SearchResponse>(msg, SearchResponse);
- this.setState({ searchResponse: Some(data) });
- window.scrollTo(0, 0);
- this.checkFinishedLoading();
- restoreScrollPosition(this.context);
- } else if (op == UserOperation.CreateCommentLike) {
- let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
- createCommentLikeRes(
- data.comment_view,
- this.state.searchResponse.map(r => r.comments).unwrapOr([])
- );
- this.setState(this.state);
- } else if (op == UserOperation.CreatePostLike) {
- let data = wsJsonToRes<PostResponse>(msg, PostResponse);
- createPostLikeFindRes(
- data.post_view,
- this.state.searchResponse.map(r => r.posts).unwrapOr([])
- );
- this.setState(this.state);
- } else if (op == UserOperation.ListCommunities) {
- let data = wsJsonToRes<ListCommunitiesResponse>(
- msg,
- ListCommunitiesResponse
- );
- this.setState({ communities: data.communities });
- this.setupCommunityFilter();
- } else if (op == UserOperation.ResolveObject) {
- let data = wsJsonToRes<ResolveObjectResponse>(msg, ResolveObjectResponse);
- this.setState({ resolveObjectResponse: Some(data) });
- this.checkFinishedLoading();
+ async updateUrl({
+ q,
+ type,
+ listingType,
+ sort,
+ communityId,
+ creatorId,
+ page,
+ }: Partial<SearchProps>) {
+ const {
+ q: urlQ,
+ type: urlType,
+ listingType: urlListingType,
+ communityId: urlCommunityId,
+ sort: urlSort,
+ creatorId: urlCreatorId,
+ page: urlPage,
+ } = getSearchQueryParams();
+
+ let query = q ?? this.state.searchText ?? urlQ;
+
+ if (query && query.length > 0) {
+ query = encodeURIComponent(query);
}
- }
- checkFinishedLoading() {
- if (
- this.state.searchResponse.isSome() &&
- this.state.resolveObjectResponse.isSome()
- ) {
- this.setState({ loading: false });
- }
+ const queryParams: QueryParams<SearchProps> = {
+ q: query,
+ type: type ?? urlType,
+ listingType: listingType ?? urlListingType,
+ communityId: getUpdatedSearchId(communityId, urlCommunityId),
+ creatorId: getUpdatedSearchId(creatorId, urlCreatorId),
+ page: (page ?? urlPage).toString(),
+ sort: sort ?? urlSort,
+ };
+
+ this.props.history.push(`/search${getQueryString(queryParams)}`);
}
}