13 } from "@utils/helpers";
14 import type { QueryParams } from "@utils/types";
15 import { RouteDataResponse } from "@utils/types";
16 import { Component, linkEvent } from "inferno";
21 ListCommunitiesResponse,
24 } from "lemmy-js-client";
25 import { InitialFetchRequest } from "../../interfaces";
26 import { FirstLoadService, I18NextService } from "../../services";
27 import { HttpService, RequestState } from "../../services/HttpService";
28 import { HtmlTags } from "../common/html-tags";
29 import { Spinner } from "../common/icon";
30 import { ListingTypeSelect } from "../common/listing-type-select";
31 import { Paginator } from "../common/paginator";
32 import { SortSelect } from "../common/sort-select";
33 import { CommunityLink } from "./community-link";
35 const communityLimit = 50;
37 type CommunitiesData = RouteDataResponse<{
38 listCommunitiesResponse: ListCommunitiesResponse;
41 interface CommunitiesState {
42 listCommunitiesResponse: RequestState<ListCommunitiesResponse>;
43 siteRes: GetSiteResponse;
45 isIsomorphic: boolean;
48 interface CommunitiesProps {
49 listingType: ListingType;
54 function getListingTypeFromQuery(listingType?: string): ListingType {
55 return listingType ? (listingType as ListingType) : "Local";
58 function getSortTypeFromQuery(type?: string): SortType {
59 return type ? (type as SortType) : "TopMonth";
62 export class Communities extends Component<any, CommunitiesState> {
63 private isoData = setIsoData<CommunitiesData>(this.context);
64 state: CommunitiesState = {
65 listCommunitiesResponse: { state: "empty" },
66 siteRes: this.isoData.site_res,
71 constructor(props: any, context: any) {
72 super(props, context);
73 this.handlePageChange = this.handlePageChange.bind(this);
74 this.handleSortChange = this.handleSortChange.bind(this);
75 this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
77 // Only fetch the data if coming from another route
78 if (FirstLoadService.isFirstLoad) {
79 const { listCommunitiesResponse } = this.isoData.routeData;
83 listCommunitiesResponse,
89 async componentDidMount() {
90 if (!this.state.isIsomorphic) {
95 get documentTitle(): string {
96 return `${I18NextService.i18n.t("communities")} - ${
97 this.state.siteRes.site_view.site.name
102 switch (this.state.listCommunitiesResponse.state) {
110 const { listingType, sort, page } = this.getCommunitiesQueryParams();
113 <h1 className="h4 mb-4">
114 {I18NextService.i18n.t("list_of_communities")}
116 <div className="row g-3 align-items-center mb-2">
117 <div className="col-auto">
120 showLocal={showLocal(this.isoData)}
122 onChange={this.handleListingTypeChange}
125 <div className="col-auto me-auto">
126 <SortSelect sort={sort} onChange={this.handleSortChange} />
128 <div className="col-auto">{this.searchForm()}</div>
131 <div className="table-responsive">
134 className="table table-sm table-hover"
136 <thead className="pointer">
138 <th>{I18NextService.i18n.t("name")}</th>
139 <th className="text-right">
140 {I18NextService.i18n.t("subscribers")}
142 <th className="text-right">
143 {I18NextService.i18n.t("users")} /{" "}
144 {I18NextService.i18n.t("month")}
146 <th className="text-right d-none d-lg-table-cell">
147 {I18NextService.i18n.t("posts")}
149 <th className="text-right d-none d-lg-table-cell">
150 {I18NextService.i18n.t("comments")}
156 {this.state.listCommunitiesResponse.data.communities.map(
158 <tr key={cv.community.id}>
160 <CommunityLink community={cv.community} />
162 <td className="text-right">
163 {numToSI(cv.counts.subscribers)}
165 <td className="text-right">
166 {numToSI(cv.counts.users_active_month)}
168 <td className="text-right d-none d-lg-table-cell">
169 {numToSI(cv.counts.posts)}
171 <td className="text-right d-none d-lg-table-cell">
172 {numToSI(cv.counts.comments)}
174 <td className="text-right">
175 {cv.subscribed === "Subscribed" && (
177 className="btn btn-link d-inline-block"
181 communityId: cv.community.id,
187 {I18NextService.i18n.t("unsubscribe")}
190 {cv.subscribed === "NotSubscribed" && (
192 className="btn btn-link d-inline-block"
196 communityId: cv.community.id,
202 {I18NextService.i18n.t("subscribe")}
205 {cv.subscribed === "Pending" && (
206 <div className="text-warning d-inline-block">
207 {I18NextService.i18n.t("subscribe_pending")}
217 <Paginator page={page} onChange={this.handlePageChange} />
226 <div className="communities container-lg">
228 title={this.documentTitle}
229 path={this.context.router.route.match.url}
231 {this.renderListings()}
238 <form className="row" onSubmit={linkEvent(this, this.handleSearchSubmit)}>
239 <div className="col-auto">
242 id="communities-search"
243 className="form-control"
244 value={this.state.searchText}
245 placeholder={`${I18NextService.i18n.t("search")}...`}
246 onInput={linkEvent(this, this.handleSearchChange)}
251 <div className="col-auto">
252 <label className="visually-hidden" htmlFor="communities-search">
253 {I18NextService.i18n.t("search")}
255 <button type="submit" className="btn btn-secondary">
256 <span>{I18NextService.i18n.t("search")}</span>
263 async updateUrl({ listingType, sort, page }: Partial<CommunitiesProps>) {
265 listingType: urlListingType,
268 } = this.getCommunitiesQueryParams();
270 const queryParams: QueryParams<CommunitiesProps> = {
271 listingType: listingType ?? urlListingType,
272 sort: sort ?? urlSort,
273 page: (page ?? urlPage)?.toString(),
276 this.props.history.push(`/communities${getQueryString(queryParams)}`);
278 await this.refetch();
281 handlePageChange(page: number) {
282 this.updateUrl({ page });
285 handleSortChange(val: SortType) {
286 this.updateUrl({ sort: val, page: 1 });
289 handleListingTypeChange(val: ListingType) {
296 handleSearchChange(i: Communities, event: any) {
297 i.setState({ searchText: event.target.value });
300 handleSearchSubmit(i: Communities, event: any) {
301 event.preventDefault();
302 const searchParamEncoded = encodeURIComponent(i.state.searchText);
303 i.context.router.history.push(
304 `/search?q=${searchParamEncoded}&type=Communities`,
308 static async fetchInitialData({
309 query: { listingType, sort, page },
312 }: InitialFetchRequest<
313 QueryParams<CommunitiesProps>
314 >): Promise<CommunitiesData> {
315 const listCommunitiesForm: ListCommunities = {
316 type_: getListingTypeFromQuery(listingType),
317 sort: getSortTypeFromQuery(sort),
318 limit: communityLimit,
319 page: getPageFromString(page),
324 listCommunitiesResponse: await client.listCommunities(
330 getCommunitiesQueryParams() {
331 return getQueryParams<CommunitiesProps>({
332 listingType: getListingTypeFromQuery,
333 sort: getSortTypeFromQuery,
334 page: getPageFromString,
338 async handleFollow(data: {
343 const res = await HttpService.client.followCommunity({
344 community_id: data.communityId,
346 auth: myAuthRequired(),
348 data.i.findAndUpdateCommunity(res);
352 this.setState({ listCommunitiesResponse: { state: "loading" } });
354 const { listingType, sort, page } = this.getCommunitiesQueryParams();
357 listCommunitiesResponse: await HttpService.client.listCommunities({
360 limit: communityLimit,
366 window.scrollTo(0, 0);
369 findAndUpdateCommunity(res: RequestState<CommunityResponse>) {
372 s.listCommunitiesResponse.state === "success" &&
373 res.state === "success"
375 s.listCommunitiesResponse.data.communities = editCommunity(
376 res.data.community_view,
377 s.listCommunitiesResponse.data.communities,