+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,
+ GetCommunityResponse,
GetPersonDetails,
+ GetPersonDetailsResponse,
+ GetSiteResponse,
ListCommunities,
ListCommunitiesResponse,
ListingType,
- PersonViewSafe,
- PostResponse,
+ PersonView,
PostView,
ResolveObject,
ResolveObjectResponse,
Search as SearchForm,
SearchResponse,
SearchType,
- Site,
SortType,
- UserOperation,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
-import { InitialFetchRequest } from "shared/interfaces";
-import { i18n } from "../i18next";
-import { WebSocketService } from "../services";
-import {
- authField,
- capitalizeFirstLetter,
- choicesConfig,
- commentsToFlatNodes,
- communitySelectName,
- communityToChoice,
- createCommentLikeRes,
- createPostLikeFindRes,
- debounce,
- fetchCommunities,
- fetchLimit,
- fetchUsers,
- isBrowser,
- numToSI,
- personSelectName,
- personToChoice,
- restoreScrollPosition,
- routeListingTypeToEnum,
- routeSearchTypeToEnum,
- routeSortTypeToEnum,
- saveScrollPosition,
- setIsoData,
- setOptionalAuth,
- showLocal,
- toast,
- wsClient,
- wsJsonToRes,
- wsSubscribe,
- wsUserOp,
-} from "../utils";
+import { fetchLimit } from "../config";
+import { CommentViewType, InitialFetchRequest } from "../interfaces";
+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;
}
-interface SearchState {
- q: string;
- type_: SearchType;
- sort: SortType;
- listingType: ListingType;
- communityId: number;
- creatorId: number;
- page: number;
+type SearchData = RouteDataResponse<{
+ communityResponse: GetCommunityResponse;
+ listCommunitiesResponse: ListCommunitiesResponse;
+ creatorDetailsResponse: GetPersonDetailsResponse;
searchResponse: SearchResponse;
- communities: CommunityView[];
- creator?: PersonViewSafe;
- loading: boolean;
- site: Site;
- searchText: string;
resolveObjectResponse: ResolveObjectResponse;
-}
+}>;
-interface UrlParams {
- q?: string;
- type_?: SearchType;
- sort?: SortType;
- listingType?: ListingType;
- communityId?: number;
- creatorId?: number;
- page?: number;
+type FilterType = "creator" | "community";
+
+interface SearchState {
+ searchRes: RequestState<SearchResponse>;
+ resolveObjectRes: RequestState<ResolveObjectResponse>;
+ creatorDetailsRes: RequestState<GetPersonDetailsResponse>;
+ communitiesRes: RequestState<ListCommunitiesResponse>;
+ communityRes: RequestState<GetCommunityResponse>;
+ siteRes: GetSiteResponse;
+ 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);
- 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: {
- type_: null,
- posts: [],
- comments: [],
- communities: [],
- users: [],
- },
- resolveObjectResponse: {
- comment: null,
- post: null,
- community: null,
- person: null,
- },
- loading: true,
- site: this.isoData.site_res.site_view.site,
- communities: [],
- };
+const defaultSearchType = "All";
+const defaultSortType = "TopAll";
+const defaultListingType = "All";
- static getSearchQueryFromProps(q: string): string {
- return decodeURIComponent(q) || "";
- }
+const searchTypes = ["All", "Comments", "Posts", "Communities", "Users", "Url"];
- static getSearchTypeFromProps(type_: string): SearchType {
- return type_ ? routeSearchTypeToEnum(type_) : SearchType.All;
- }
+const getSearchQueryParams = () =>
+ getQueryParams<SearchProps>({
+ q: getSearchQueryFromQuery,
+ type: getSearchTypeFromQuery,
+ sort: getSortTypeFromQuery,
+ listingType: getListingTypeFromQuery,
+ communityId: getIdFromString,
+ creatorId: getIdFromString,
+ page: getPageFromString,
+ });
- static getSortTypeFromProps(sort: string): SortType {
- return sort ? routeSortTypeToEnum(sort) : SortType.TopAll;
- }
+const getSearchQueryFromQuery = (q?: string): string | undefined =>
+ q ? decodeURIComponent(q) : undefined;
- static getListingTypeFromProps(listingType: string): ListingType {
- return listingType ? routeListingTypeToEnum(listingType) : ListingType.All;
- }
+function getSearchTypeFromQuery(type_?: string): SearchType {
+ return type_ ? (type_ as SearchType) : defaultSearchType;
+}
- static getCommunityIdFromProps(id: string): number {
- return id ? Number(id) : 0;
- }
+function getSortTypeFromQuery(sort?: string): SortType {
+ return sort ? (sort as SortType) : defaultSortType;
+}
- static getCreatorIdFromProps(id: string): number {
- return id ? Number(id) : 0;
- }
+function getListingTypeFromQuery(listingType?: string): ListingType {
+ return listingType ? (listingType as ListingType) : defaultListingType;
+}
- static getPageFromProps(page: string): number {
- return page ? Number(page) : 1;
- }
+function postViewToCombined(data: PostView): Combined {
+ return {
+ type_: "posts",
+ data,
+ published: data.post.published,
+ };
+}
+
+function commentViewToCombined(data: CommentView): Combined {
+ return {
+ type_: "comments",
+ data,
+ published: data.comment.published,
+ };
+}
+
+function communityViewToCombined(data: CommunityView): Combined {
+ return {
+ type_: "communities",
+ data,
+ published: data.community.published,
+ };
+}
+
+function personViewSafeToCombined(data: PersonView): Combined {
+ return {
+ type_: "users",
+ data,
+ published: data.person.published,
+ };
+}
+
+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"
+ );
+
+const personListing = ({ person, counts: { comment_count } }: PersonView) =>
+ getListing(
+ <PersonListing person={person} showApubName />,
+ comment_count,
+ "number_of_comments"
+ );
+
+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>
+ </>
+ );
+}
+
+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 singleOrMultipleCommunities = this.isoData.routeData[0];
- if (singleOrMultipleCommunities.communities) {
- this.state.communities = this.isoData.routeData[0].communities;
- } else {
- this.state.communities = [this.isoData.routeData[0].community_view];
+ if (FirstLoadService.isFirstLoad) {
+ const {
+ communityResponse: communityRes,
+ creatorDetailsResponse: creatorDetailsRes,
+ listCommunitiesResponse: communitiesRes,
+ resolveObjectResponse: resolveObjectRes,
+ searchResponse: searchRes,
+ } = this.isoData.routeData;
+
+ this.state = {
+ ...this.state,
+ isIsomorphic: true,
+ };
+
+ if (creatorDetailsRes?.state === "success") {
+ this.state = {
+ ...this.state,
+ creatorSearchOptions:
+ creatorDetailsRes?.state === "success"
+ ? [personToChoice(creatorDetailsRes.data.person_view)]
+ : [],
+ creatorDetailsRes,
+ };
}
- let creator = this.isoData.routeData[1];
- if (creator?.person_view) {
- this.state.creator = this.isoData.routeData[1].person_view;
+ if (communitiesRes?.state === "success") {
+ this.state = {
+ ...this.state,
+ communitiesRes,
+ };
}
- if (this.state.q != "") {
- this.state.searchResponse = this.isoData.routeData[2];
- this.state.resolveObjectResponse = this.isoData.routeData[3];
- this.state.loading = false;
- } else {
- this.search();
+
+ if (communityRes?.state === "success") {
+ this.state = {
+ ...this.state,
+ communityRes,
+ };
+ }
+
+ if (q !== "") {
+ this.state = {
+ ...this.state,
+ };
+
+ 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: ListCommunities = {
- type_: ListingType.All,
- sort: SortType.TopAll,
- limit: fetchLimit,
- auth: authField(false),
- };
- 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]);
- if (communityId !== 0) {
- let getCommunityForm: GetCommunity = {
- id: communityId,
+ 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,
};
- setOptionalAuth(getCommunityForm, req.auth);
- promises.push(req.client.getCommunity(getCommunityForm));
+
+ communityResponse = await client.getCommunity(getCommunityForm);
} else {
- let listCommunitiesForm: ListCommunities = {
- type_: ListingType.All,
- sort: SortType.TopAll,
+ const listCommunitiesForm: ListCommunities = {
+ type_: defaultListingType,
+ sort: defaultSortType,
limit: fetchLimit,
+ auth,
};
- setOptionalAuth(listCommunitiesForm, req.auth);
- promises.push(req.client.listCommunities(listCommunitiesForm));
- }
- let creatorId = this.getCreatorIdFromProps(pathSplit[13]);
- if (creatorId !== 0) {
- let getCreatorForm: GetPersonDetails = {
- person_id: creatorId,
- };
- setOptionalAuth(getCreatorForm, req.auth);
- promises.push(req.client.getPersonDetails(getCreatorForm));
- } else {
- promises.push(Promise.resolve());
+ listCommunitiesResponse = await client.listCommunities(
+ listCommunitiesForm
+ );
}
- let form: SearchForm = {
- q: this.getSearchQueryFromProps(pathSplit[3]),
- type_: this.getSearchTypeFromProps(pathSplit[5]),
- sort: this.getSortTypeFromProps(pathSplit[7]),
- listing_type: this.getListingTypeFromProps(pathSplit[9]),
- page: this.getPageFromProps(pathSplit[15]),
- limit: fetchLimit,
+ const creator_id = getIdFromString(creatorId);
+ let creatorDetailsResponse: RequestState<GetPersonDetailsResponse> = {
+ state: "empty",
};
- if (communityId !== 0) {
- form.community_id = communityId;
- }
- if (creatorId !== 0) {
- form.creator_id = creatorId;
+ if (creator_id) {
+ const getCreatorForm: GetPersonDetails = {
+ person_id: creator_id,
+ auth,
+ };
+
+ creatorDetailsResponse = await client.getPersonDetails(getCreatorForm);
}
- setOptionalAuth(form, req.auth);
- let resolveObjectForm: ResolveObject = {
- q: this.getSearchQueryFromProps(pathSplit[3]),
- };
- setOptionalAuth(resolveObjectForm, req.auth);
+ const query = getSearchQueryFromQuery(q);
- if (form.q != "") {
- promises.push(req.client.search(form));
- promises.push(req.client.resolveObject(resolveObjectForm));
- }
+ let searchResponse: RequestState<SearchResponse> = { state: "empty" };
+ let resolveObjectResponse: RequestState<ResolveObjectResponse> = {
+ state: "empty",
+ };
- return promises;
- }
+ 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,
+ };
- 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 });
- this.search();
+ 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 {
- if (this.state.q) {
- return `${i18n.t("search")} - ${this.state.q} - ${this.state.site.name}`;
- } else {
- return `${i18n.t("search")} - ${this.state.site.name}`;
- }
+ 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 class="container">
+ <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>
- {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
- class="form-inline"
+ className="row gx-2 gy-3"
onSubmit={linkEvent(this, this.handleSearchSubmit)}
>
- <input
- type="text"
- class="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={3}
- />
- <button type="submit" class="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)}
- class="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 class="ml-2">
- <ListingTypeSelect
- type_={this.state.listingType}
- showLocal={showLocal(this.isoData)}
- onChange={this.handleListingTypeChange}
- />
- </span>
- <span class="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 class="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
- let resolveComment = this.state.resolveObjectResponse.comment;
- if (resolveComment) {
- combined.push(this.commentViewToCombined(resolveComment));
- }
- let resolvePost = this.state.resolveObjectResponse.post;
- if (resolvePost) {
- combined.push(this.postViewToCombined(resolvePost));
- }
- let resolveCommunity = this.state.resolveObjectResponse.community;
- if (resolveCommunity) {
- combined.push(this.communityViewToCombined(resolveCommunity));
- }
- let resolveUser = this.state.resolveObjectResponse.person;
- if (resolveUser) {
- combined.push(this.personViewSafeToCombined(resolveUser));
+ 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
- combined.push(
- ...this.state.searchResponse.comments.map(e =>
- this.commentViewToCombined(e)
- )
- );
- combined.push(
- ...this.state.searchResponse.posts.map(e => this.postViewToCombined(e))
- );
- combined.push(
- ...this.state.searchResponse.communities.map(e =>
- this.communityViewToCombined(e)
- )
- );
- combined.push(
- ...this.state.searchResponse.users.map(e =>
- this.personViewSafeToCombined(e)
- )
- );
+ 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 class="row">
- <div class="col-12">
- {i.type_ == "posts" && (
+ <div key={i.published} className="row">
+ <div className="col-12">
+ {i.type_ === "posts" && (
<PostListing
key={(i.data as PostView).post.id}
post_view={i.data as PostView}
showCommunity
- enableDownvotes={this.state.site.enable_downvotes}
- enableNsfw={this.state.site.enable_nsfw}
+ 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={[{ comment_view: i.data as CommentView }]}
+ nodes={[
+ {
+ comment_view: i.data as CommentView,
+ children: [],
+ depth: 0,
+ },
+ ]}
+ viewType={CommentViewType.Flat}
+ viewOnly
locked
noIndent
- enableDownvotes={this.state.site.enable_downvotes}
+ 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.userListing(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 : [];
- let resolveComment = this.state.resolveObjectResponse.comment;
- if (resolveComment) {
- comments.push(resolveComment);
+ if (
+ resolveObjectResponse.state === "success" &&
+ resolveObjectResponse.data.comment
+ ) {
+ comments.unshift(resolveObjectResponse.data.comment);
}
- comments.push(...this.state.searchResponse.comments);
-
return (
<CommentNodes
nodes={commentsToFlatNodes(comments)}
+ viewType={CommentViewType.Flat}
+ viewOnly
locked
noIndent
- enableDownvotes={this.state.site.enable_downvotes}
+ 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 : [];
- let resolvePost = this.state.resolveObjectResponse.post;
- if (resolvePost) {
- posts.push(resolvePost);
+ if (
+ resolveObjectResponse.state === "success" &&
+ resolveObjectResponse.data.post
+ ) {
+ posts.unshift(resolveObjectResponse.data.post);
}
- posts.push(...this.state.searchResponse.posts);
-
return (
<>
- {posts.map(post => (
- <div class="row">
- <div class="col-12">
+ {posts.map(pv => (
+ <div key={pv.post.id} className="row">
+ <div className="col-12">
<PostListing
- post_view={post}
+ post_view={pv}
showCommunity
- enableDownvotes={this.state.site.enable_downvotes}
- enableNsfw={this.state.site.enable_nsfw}
+ 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 : [];
- let resolveCommunity = this.state.resolveObjectResponse.community;
- if (resolveCommunity) {
- communities.push(resolveCommunity);
+ if (
+ resolveObjectResponse.state === "success" &&
+ resolveObjectResponse.data.community
+ ) {
+ communities.unshift(resolveObjectResponse.data.community);
}
- communities.push(...this.state.searchResponse.communities);
return (
<>
- {communities.map(community => (
- <div class="row">
- <div class="col-12">{this.communityListing(community)}</div>
+ {communities.map(cv => (
+ <div key={cv.community.id} className="row">
+ <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 : [];
- let resolveUser = this.state.resolveObjectResponse.person;
- if (resolveUser) {
- users.push(resolveUser);
+ if (
+ resolveObjectResponse.state === "success" &&
+ resolveObjectResponse.data.person
+ ) {
+ users.unshift(resolveObjectResponse.data.person);
}
- users.push(...this.state.searchResponse.users);
return (
<>
- {users.map(user => (
- <div class="row">
- <div class="col-12">{this.userListing(user)}</div>
+ {users.map(pvs => (
+ <div key={pvs.person.id} className="row">
+ <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;
- userListing(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>,
- ];
- }
+ const searchCount =
+ r.state === "success"
+ ? r.data.posts.length +
+ r.data.comments.length +
+ r.data.communities.length +
+ r.data.users.length
+ : 0;
- communityFilter() {
- return (
- <div class="form-group col-sm-6">
- <label class="col-form-label" htmlFor="community-filter">
- {i18n.t("community")}
- </label>
- <div>
- <select
- class="form-control"
- id="community-filter"
- value={this.state.communityId}
- >
- <option value="0">{i18n.t("all")}</option>
- {this.state.communities.map(cv => (
- <option value={cv.community.id}>{communitySelectName(cv)}</option>
- ))}
- </select>
- </div>
- </div>
- );
- }
+ const resObjCount =
+ resolveRes.state === "success"
+ ? resolveRes.data.post ||
+ resolveRes.data.person ||
+ resolveRes.data.community ||
+ resolveRes.data.comment
+ ? 1
+ : 0
+ : 0;
- creatorFilter() {
- return (
- <div class="form-group col-sm-6">
- <label class="col-form-label" htmlFor="creator-filter">
- {capitalizeFirstLetter(i18n.t("creator"))}
- </label>
- <div>
- <select
- class="form-control"
- id="creator-filter"
- value={this.state.creatorId}
- >
- <option value="0">{i18n.t("all")}</option>
- {this.state.creator && (
- <option value={this.state.creator.person.id}>
- {personSelectName(this.state.creator)}
- </option>
- )}
- </select>
- </div>
- </div>
- );
+ return resObjCount + searchCount;
}
- resultsCount(): number {
- let res = this.state.searchResponse;
- let resObj = this.state.resolveObjectResponse;
- let resObjCount =
- resObj.post || resObj.person || resObj.community || resObj.comment
- ? 1
- : 0;
- return (
- res.posts.length +
- res.comments.length +
- res.communities.length +
- res.users.length +
- resObjCount
- );
- }
+ async search() {
+ const auth = myAuth();
+ const { searchText: q } = this.state;
+ const { communityId, creatorId, type, sort, listingType, page } =
+ getSearchQueryParams();
+
+ 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);
- handlePageChange(page: number) {
- this.updateUrl({ page });
+ if (auth) {
+ this.setState({ resolveObjectRes: { state: "loading" } });
+ this.setState({
+ resolveObjectRes: await HttpService.silent_client.resolveObject({
+ q,
+ auth,
+ }),
+ });
+ }
+ }
}
- search() {
- let form: SearchForm = {
- q: this.state.q,
- type_: this.state.type_,
- sort: this.state.sort,
- listing_type: this.state.listingType,
- page: this.state.page,
- limit: fetchLimit,
- auth: authField(false),
- };
- if (this.state.communityId !== 0) {
- form.community_id = this.state.communityId;
+ handleCreatorSearch = debounce(async (text: string) => {
+ const { creatorId } = getSearchQueryParams();
+ const { creatorSearchOptions } = this.state;
+ const newOptions: Choice[] = [];
+
+ this.setState({ searchCreatorLoading: true });
+
+ const selectedChoice = creatorSearchOptions.find(
+ choice => getIdFromString(choice.value) === creatorId
+ );
+
+ if (selectedChoice) {
+ newOptions.push(selectedChoice);
}
- if (this.state.creatorId !== 0) {
- form.creator_id = this.state.creatorId;
+
+ if (text.length > 0) {
+ newOptions.push(...(await fetchUsers(text)).map(personToChoice));
}
- let resolveObjectForm: ResolveObject = {
- q: this.state.q,
- auth: authField(false),
- };
+ this.setState({
+ searchCreatorLoading: false,
+ creatorSearchOptions: newOptions,
+ });
+ });
- if (this.state.q != "") {
- WebSocketService.Instance.send(wsClient.search(form));
- WebSocketService.Instance.send(wsClient.resolveObject(resolveObjectForm));
- }
- }
+ handleCommunitySearch = debounce(async (text: string) => {
+ const { communityId } = getSearchQueryParams();
+ const { communitySearchOptions } = this.state;
+ this.setState({
+ searchCommunitiesLoading: true,
+ });
- 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) => {
- 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);
- }, 400),
- false
- );
- }
+ const newOptions: Choice[] = [];
+
+ 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) => {
- 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);
- }, 400),
- 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") {
- toast(i18n.t(msg.error), "danger");
- return;
- } else {
- this.setState({
- resolveObjectResponse: {
- comment: null,
- community: null,
- person: null,
- post: null,
- },
- });
- }
- } else if (op == UserOperation.Search) {
- let data = wsJsonToRes<SearchResponse>(msg).data;
- this.state.searchResponse = data;
- this.state.loading = false;
- window.scrollTo(0, 0);
- this.setState(this.state);
- restoreScrollPosition(this.context);
- } else if (op == UserOperation.CreateCommentLike) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
- createCommentLikeRes(
- data.comment_view,
- this.state.searchResponse.comments
- );
- this.setState(this.state);
- } else if (op == UserOperation.CreatePostLike) {
- let data = wsJsonToRes<PostResponse>(msg).data;
- createPostLikeFindRes(data.post_view, this.state.searchResponse.posts);
- this.setState(this.state);
- } else if (op == UserOperation.ListCommunities) {
- let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
- this.state.communities = data.communities;
- this.setState(this.state);
- this.setupCommunityFilter();
- } else if (op == UserOperation.ResolveObject) {
- let data = wsJsonToRes<ResolveObjectResponse>(msg).data;
- this.state.resolveObjectResponse = data;
- this.setState(this.state);
+ 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);
}
+
+ 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)}`);
}
}