import { Component, linkEvent } from "inferno"; import { Subscription } from "rxjs"; import { UserOperation, PostView, CommentView, CommunityView, UserViewSafe, SortType, Search as SearchForm, SearchResponse, SearchType, PostResponse, CommentResponse, Site, } from "lemmy-js-client"; import { WebSocketService } from "../services"; import { wsJsonToRes, fetchLimit, routeSearchTypeToEnum, routeSortTypeToEnum, toast, createCommentLikeRes, createPostLikeFindRes, commentsToFlatNodes, setIsoData, wsSubscribe, wsUserOp, wsClient, authField, setOptionalAuth, saveScrollPosition, restoreScrollPosition, } from "../utils"; import { PostListing } from "./post-listing"; import { HtmlTags } from "./html-tags"; import { Spinner } from "./icon"; import { UserListing } from "./user-listing"; import { CommunityLink } from "./community-link"; import { SortSelect } from "./sort-select"; import { CommentNodes } from "./comment-nodes"; import { i18n } from "../i18next"; import { InitialFetchRequest } from "shared/interfaces"; interface SearchProps { q: string; type_: SearchType; sort: SortType; page: number; } interface SearchState { q: string; type_: SearchType; sort: SortType; page: number; searchResponse: SearchResponse; loading: boolean; site: Site; searchText: string; } interface UrlParams { q?: string; type_?: SearchType; sort?: SortType; page?: number; } export class Search extends Component { private isoData = setIsoData(this.context); 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), page: Search.getPageFromProps(this.props.match.params.page), searchText: Search.getSearchQueryFromProps(this.props.match.params.q), searchResponse: { type_: null, posts: [], comments: [], communities: [], users: [], }, loading: false, site: this.isoData.site_res.site_view.site, }; static getSearchQueryFromProps(q: string): string { return decodeURIComponent(q) || ""; } static getSearchTypeFromProps(type_: string): SearchType { return type_ ? routeSearchTypeToEnum(type_) : SearchType.All; } static getSortTypeFromProps(sort: string): SortType { return sort ? routeSortTypeToEnum(sort) : SortType.TopAll; } static getPageFromProps(page: string): number { return page ? Number(page) : 1; } constructor(props: any, context: any) { super(props, context); this.state = this.emptyState; this.handleSortChange = this.handleSortChange.bind(this); this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); // Only fetch the data if coming from another route if (this.state.q != "") { if (this.isoData.path == this.context.router.route.match.url) { this.state.searchResponse = this.isoData.routeData[0]; this.state.loading = false; } else { this.search(); } } } componentWillUnmount() { this.subscription.unsubscribe(); saveScrollPosition(this.context); } 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), page: Search.getPageFromProps(props.match.params.page), }; } static fetchInitialData(req: InitialFetchRequest): Promise[] { let pathSplit = req.path.split("/"); let promises: Promise[] = []; let form: SearchForm = { q: this.getSearchQueryFromProps(pathSplit[3]), type_: this.getSearchTypeFromProps(pathSplit[5]), sort: this.getSortTypeFromProps(pathSplit[7]), page: this.getPageFromProps(pathSplit[9]), limit: fetchLimit, }; setOptionalAuth(form, req.auth); if (form.q != "") { promises.push(req.client.search(form)); } return promises; } componentDidUpdate(_: any, lastState: SearchState) { if ( lastState.q !== this.state.q || lastState.type_ !== this.state.type_ || lastState.sort !== this.state.sort || lastState.page !== this.state.page ) { this.setState({ loading: true, searchText: this.state.q }); this.search(); } } 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}`; } } render() { return (
{i18n.t("search")}
{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.resultsCount() == 0 && {i18n.t("no_results")}} {this.paginator()}
); } searchForm() { return (
); } selects() { return (
); } all() { let combined: { type_: string; data: CommentView | PostView | CommunityView | UserViewSafe; published: string; }[] = []; let comments = this.state.searchResponse.comments.map(e => { return { type_: "comments", data: e, published: e.comment.published }; }); let posts = this.state.searchResponse.posts.map(e => { return { type_: "posts", data: e, published: e.post.published }; }); let communities = this.state.searchResponse.communities.map(e => { return { type_: "communities", data: e, published: e.community.published, }; }); let users = this.state.searchResponse.users.map(e => { return { type_: "users", data: e, published: e.user.published }; }); combined.push(...comments); combined.push(...posts); combined.push(...communities); combined.push(...users); // Sort it if (this.state.sort == SortType.New) { combined.sort((a, b) => b.published.localeCompare(a.published)); } else { combined.sort( (a, b) => ((b.data as CommentView | PostView).counts.score | (b.data as CommunityView).counts.subscribers | (b.data as UserViewSafe).counts.comment_score) - ((a.data as CommentView | PostView).counts.score | (a.data as CommunityView).counts.subscribers | (a.data as UserViewSafe).counts.comment_score) ); } return (
{combined.map(i => (
{i.type_ == "posts" && ( )} {i.type_ == "comments" && ( )} {i.type_ == "communities" && (
{this.communityListing(i.data as CommunityView)}
)} {i.type_ == "users" && (
{this.userListing(i.data as UserViewSafe)}
)}
))}
); } comments() { return ( ); } posts() { return ( <> {this.state.searchResponse.posts.map(post => (
))} ); } communities() { return ( <> {this.state.searchResponse.communities.map(community => (
{this.communityListing(community)}
))} ); } communityListing(community_view: CommunityView) { return ( <> {` - ${i18n.t("number_of_subscribers", { count: community_view.counts.subscribers, })} `} ); } userListing(user_view: UserViewSafe) { return [ , {` - ${i18n.t("number_of_comments", { count: user_view.counts.comment_count, })}`}, ]; } users() { return ( <> {this.state.searchResponse.users.map(user => (
{this.userListing(user)}
))} ); } paginator() { return (
{this.state.page > 1 && ( )} {this.resultsCount() > 0 && ( )}
); } resultsCount(): number { let res = this.state.searchResponse; return ( res.posts.length + res.comments.length + res.communities.length + res.users.length ); } nextPage(i: Search) { i.updateUrl({ page: i.state.page + 1 }); } prevPage(i: Search) { i.updateUrl({ page: i.state.page - 1 }); } search() { let form: SearchForm = { q: this.state.q, type_: this.state.type_, sort: this.state.sort, page: this.state.page, limit: fetchLimit, auth: authField(false), }; if (this.state.q != "") { WebSocketService.Instance.send(wsClient.search(form)); } } handleSortChange(val: SortType) { this.updateUrl({ sort: val, page: 1 }); } handleTypeChange(i: Search, event: any) { i.updateUrl({ type_: SearchType[event.target.value], page: 1, }); } handleSearchSubmit(i: Search, event: any) { event.preventDefault(); i.updateUrl({ q: i.state.searchText, type_: i.state.type_, sort: i.state.sort, page: i.state.page, }); } handleQChange(i: Search, event: any) { 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 sortStr = paramUpdates.sort || this.state.sort; const page = paramUpdates.page || this.state.page; this.props.history.push( `/search/q/${qStrEncoded}/type/${typeStr}/sort/${sortStr}/page/${page}` ); } parseMessage(msg: any) { console.log(msg); let op = wsUserOp(msg); if (msg.error) { toast(i18n.t(msg.error), "danger"); return; } else if (op == UserOperation.Search) { let data = wsJsonToRes(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(msg).data; createCommentLikeRes( data.comment_view, this.state.searchResponse.comments ); this.setState(this.state); } else if (op == UserOperation.CreatePostLike) { let data = wsJsonToRes(msg).data; createPostLikeFindRes(data.post_view, this.state.searchResponse.posts); this.setState(this.state); } } }