1 import { Component, linkEvent } from "inferno";
2 import { Subscription } from "rxjs";
3 import { DataType, InitialFetchRequest } from "../interfaces";
15 AddModToCommunityResponse,
16 BanFromCommunityResponse,
23 ListCategoriesResponse,
24 } from "lemmy-js-client";
25 import { UserService, WebSocketService } from "../services";
26 import { PostListings } from "./post-listings";
27 import { CommentNodes } from "./comment-nodes";
28 import { HtmlTags } from "./html-tags";
29 import { SortSelect } from "./sort-select";
30 import { DataTypeSelect } from "./data-type-select";
31 import { Sidebar } from "./sidebar";
32 import { CommunityLink } from "./community-link";
33 import { BannerIconHeader } from "./banner-icon-header";
34 import { Icon, Spinner } from "./icon";
45 createPostLikeFindRes,
58 restoreScrollPosition,
60 import { i18n } from "../i18next";
63 communityRes: GetCommunityResponse;
64 siteRes: GetSiteResponse;
66 communityName: string;
67 communityLoading: boolean;
68 postsLoading: boolean;
69 commentsLoading: boolean;
71 comments: CommentView[];
75 categories: Category[];
78 interface CommunityProps {
90 export class Community extends Component<any, State> {
91 private isoData = setIsoData(this.context);
92 private subscription: Subscription;
93 private emptyState: State = {
94 communityRes: undefined,
95 communityId: Number(this.props.match.params.id),
96 communityName: this.props.match.params.name,
97 communityLoading: true,
99 commentsLoading: true,
102 dataType: getDataTypeFromProps(this.props),
103 sort: getSortTypeFromProps(this.props),
104 page: getPageFromProps(this.props),
105 siteRes: this.isoData.site_res,
109 constructor(props: any, context: any) {
110 super(props, context);
112 this.state = this.emptyState;
113 this.handleSortChange = this.handleSortChange.bind(this);
114 this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
116 this.parseMessage = this.parseMessage.bind(this);
117 this.subscription = wsSubscribe(this.parseMessage);
119 // Only fetch the data if coming from another route
120 if (this.isoData.path == this.context.router.route.match.url) {
121 this.state.communityRes = this.isoData.routeData[0];
122 if (this.state.dataType == DataType.Post) {
123 this.state.posts = this.isoData.routeData[1].posts;
125 this.state.comments = this.isoData.routeData[1].comments;
127 this.state.categories = this.isoData.routeData[2].categories;
128 this.state.communityLoading = false;
129 this.state.postsLoading = false;
130 this.state.commentsLoading = false;
132 this.fetchCommunity();
134 WebSocketService.Instance.send(wsClient.listCategories());
140 let form: GetCommunity = {
141 id: this.state.communityId ? this.state.communityId : null,
142 name: this.state.communityName ? this.state.communityName : null,
143 auth: authField(false),
145 WebSocketService.Instance.send(wsClient.getCommunity(form));
148 componentWillUnmount() {
149 saveScrollPosition(this.context);
150 this.subscription.unsubscribe();
151 window.isoData.path = undefined;
154 static getDerivedStateFromProps(props: any): CommunityProps {
156 dataType: getDataTypeFromProps(props),
157 sort: getSortTypeFromProps(props),
158 page: getPageFromProps(props),
162 static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
163 let pathSplit = req.path.split("/");
164 let promises: Promise<any>[] = [];
166 // It can be /c/main, or /c/1
167 let idOrName = pathSplit[2];
170 if (isNaN(Number(idOrName))) {
173 id = Number(idOrName);
176 let communityForm: GetCommunity = id ? { id } : { name: name_ };
177 setOptionalAuth(communityForm, req.auth);
178 promises.push(req.client.getCommunity(communityForm));
180 let dataType: DataType = pathSplit[4]
181 ? DataType[pathSplit[4]]
184 let sort: SortType = pathSplit[6]
185 ? SortType[pathSplit[6]]
186 : UserService.Instance.user
187 ? Object.values(SortType)[UserService.Instance.user.default_sort_type]
190 let page = pathSplit[8] ? Number(pathSplit[8]) : 1;
192 if (dataType == DataType.Post) {
193 let getPostsForm: GetPosts = {
197 type_: ListingType.Community,
199 setOptionalAuth(getPostsForm, req.auth);
200 this.setIdOrName(getPostsForm, id, name_);
201 promises.push(req.client.getPosts(getPostsForm));
203 let getCommentsForm: GetComments = {
207 type_: ListingType.Community,
209 setOptionalAuth(getCommentsForm, req.auth);
210 this.setIdOrName(getCommentsForm, id, name_);
211 promises.push(req.client.getComments(getCommentsForm));
214 promises.push(req.client.listCategories());
219 static setIdOrName(obj: any, id: number, name_: string) {
221 obj.community_id = id;
223 obj.community_name = name_;
227 componentDidUpdate(_: any, lastState: State) {
229 lastState.dataType !== this.state.dataType ||
230 lastState.sort !== this.state.sort ||
231 lastState.page !== this.state.page
233 this.setState({ postsLoading: true, commentsLoading: true });
238 get documentTitle(): string {
239 return `${this.state.communityRes.community_view.community.title} - ${this.state.siteRes.site_view.site.name}`;
243 let cv = this.state.communityRes?.community_view;
245 <div class="container">
246 {this.state.communityLoading ? (
252 <div class="col-12 col-md-8">
254 title={this.documentTitle}
255 path={this.context.router.route.match.url}
256 description={cv.community.description}
257 image={cv.community.icon}
259 {this.communityInfo()}
264 <div class="col-12 col-md-4">
267 moderators={this.state.communityRes.moderators}
268 admins={this.state.siteRes.admins}
269 online={this.state.communityRes.online}
270 enableNsfw={this.state.siteRes.site_view.site.enable_nsfw}
271 categories={this.state.categories}
281 let site = this.state.siteRes.site_view.site;
282 return this.state.dataType == DataType.Post ? (
283 this.state.postsLoading ? (
289 posts={this.state.posts}
291 enableDownvotes={site.enable_downvotes}
292 enableNsfw={site.enable_nsfw}
295 ) : this.state.commentsLoading ? (
301 nodes={commentsToFlatNodes(this.state.comments)}
304 enableDownvotes={site.enable_downvotes}
310 let community = this.state.communityRes.community_view.community;
313 <BannerIconHeader banner={community.banner} icon={community.icon} />
314 <h5 class="mb-0">{community.title}</h5>
316 community={community}
332 type_={this.state.dataType}
333 onChange={this.handleDataTypeChange}
337 <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
340 href={communityRSSUrl(
341 this.state.communityRes.community_view.community.actor_id,
347 <Icon icon="rss" classes="text-muted small" />
356 {this.state.page > 1 && (
358 class="btn btn-secondary mr-1"
359 onClick={linkEvent(this, this.prevPage)}
364 {this.state.posts.length > 0 && (
366 class="btn btn-secondary"
367 onClick={linkEvent(this, this.nextPage)}
376 nextPage(i: Community) {
377 i.updateUrl({ page: i.state.page + 1 });
378 window.scrollTo(0, 0);
381 prevPage(i: Community) {
382 i.updateUrl({ page: i.state.page - 1 });
383 window.scrollTo(0, 0);
386 handleSortChange(val: SortType) {
387 this.updateUrl({ sort: val, page: 1 });
388 window.scrollTo(0, 0);
391 handleDataTypeChange(val: DataType) {
392 this.updateUrl({ dataType: DataType[val], page: 1 });
393 window.scrollTo(0, 0);
396 updateUrl(paramUpdates: UrlParams) {
397 const dataTypeStr = paramUpdates.dataType || DataType[this.state.dataType];
398 const sortStr = paramUpdates.sort || this.state.sort;
399 const page = paramUpdates.page || this.state.page;
401 let typeView = this.state.communityName
402 ? `/c/${this.state.communityName}`
403 : `/community/${this.state.communityId}`;
405 this.props.history.push(
406 `${typeView}/data_type/${dataTypeStr}/sort/${sortStr}/page/${page}`
411 if (this.state.dataType == DataType.Post) {
412 let form: GetPosts = {
413 page: this.state.page,
415 sort: this.state.sort,
416 type_: ListingType.Community,
417 community_id: this.state.communityId,
418 community_name: this.state.communityName,
419 auth: authField(false),
421 WebSocketService.Instance.send(wsClient.getPosts(form));
423 let form: GetComments = {
424 page: this.state.page,
426 sort: this.state.sort,
427 type_: ListingType.Community,
428 community_id: this.state.communityId,
429 community_name: this.state.communityName,
430 auth: authField(false),
432 WebSocketService.Instance.send(wsClient.getComments(form));
436 parseMessage(msg: any) {
437 let op = wsUserOp(msg);
439 toast(i18n.t(msg.error), "danger");
440 this.context.router.history.push("/");
442 } else if (msg.reconnect) {
443 WebSocketService.Instance.send(
444 wsClient.communityJoin({
445 community_id: this.state.communityRes.community_view.community.id,
449 } else if (op == UserOperation.GetCommunity) {
450 let data = wsJsonToRes<GetCommunityResponse>(msg).data;
451 this.state.communityRes = data;
452 this.state.communityLoading = false;
453 this.setState(this.state);
454 // TODO why is there no auth in this form?
455 WebSocketService.Instance.send(
456 wsClient.communityJoin({
457 community_id: data.community_view.community.id,
461 op == UserOperation.EditCommunity ||
462 op == UserOperation.DeleteCommunity ||
463 op == UserOperation.RemoveCommunity
465 let data = wsJsonToRes<CommunityResponse>(msg).data;
466 this.state.communityRes.community_view = data.community_view;
467 this.setState(this.state);
468 } else if (op == UserOperation.FollowCommunity) {
469 let data = wsJsonToRes<CommunityResponse>(msg).data;
470 this.state.communityRes.community_view.subscribed =
471 data.community_view.subscribed;
472 this.state.communityRes.community_view.counts.subscribers =
473 data.community_view.counts.subscribers;
474 this.setState(this.state);
475 } else if (op == UserOperation.GetPosts) {
476 let data = wsJsonToRes<GetPostsResponse>(msg).data;
477 this.state.posts = data.posts;
478 this.state.postsLoading = false;
479 this.setState(this.state);
480 restoreScrollPosition(this.context);
483 op == UserOperation.EditPost ||
484 op == UserOperation.DeletePost ||
485 op == UserOperation.RemovePost ||
486 op == UserOperation.LockPost ||
487 op == UserOperation.StickyPost ||
488 op == UserOperation.SavePost
490 let data = wsJsonToRes<PostResponse>(msg).data;
491 editPostFindRes(data.post_view, this.state.posts);
492 this.setState(this.state);
493 } else if (op == UserOperation.CreatePost) {
494 let data = wsJsonToRes<PostResponse>(msg).data;
495 this.state.posts.unshift(data.post_view);
496 notifyPost(data.post_view, this.context.router);
497 this.setState(this.state);
498 } else if (op == UserOperation.CreatePostLike) {
499 let data = wsJsonToRes<PostResponse>(msg).data;
500 createPostLikeFindRes(data.post_view, this.state.posts);
501 this.setState(this.state);
502 } else if (op == UserOperation.AddModToCommunity) {
503 let data = wsJsonToRes<AddModToCommunityResponse>(msg).data;
504 this.state.communityRes.moderators = data.moderators;
505 this.setState(this.state);
506 } else if (op == UserOperation.BanFromCommunity) {
507 let data = wsJsonToRes<BanFromCommunityResponse>(msg).data;
509 // TODO this might be incorrect
511 .filter(p => p.creator.id == data.user_view.user.id)
512 .forEach(p => (p.creator_banned_from_community = data.banned));
514 this.setState(this.state);
515 } else if (op == UserOperation.GetComments) {
516 let data = wsJsonToRes<GetCommentsResponse>(msg).data;
517 this.state.comments = data.comments;
518 this.state.commentsLoading = false;
519 this.setState(this.state);
521 op == UserOperation.EditComment ||
522 op == UserOperation.DeleteComment ||
523 op == UserOperation.RemoveComment
525 let data = wsJsonToRes<CommentResponse>(msg).data;
526 editCommentRes(data.comment_view, this.state.comments);
527 this.setState(this.state);
528 } else if (op == UserOperation.CreateComment) {
529 let data = wsJsonToRes<CommentResponse>(msg).data;
531 // Necessary since it might be a user reply
533 this.state.comments.unshift(data.comment_view);
534 this.setState(this.state);
536 } else if (op == UserOperation.SaveComment) {
537 let data = wsJsonToRes<CommentResponse>(msg).data;
538 saveCommentRes(data.comment_view, this.state.comments);
539 this.setState(this.state);
540 } else if (op == UserOperation.CreateCommentLike) {
541 let data = wsJsonToRes<CommentResponse>(msg).data;
542 createCommentLikeRes(data.comment_view, this.state.comments);
543 this.setState(this.state);
544 } else if (op == UserOperation.ListCategories) {
545 let data = wsJsonToRes<ListCategoriesResponse>(msg).data;
546 this.state.categories = data.categories;
547 this.setState(this.state);