1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { Subscription } from 'rxjs';
4 import { retryWhen, delay, take } from 'rxjs/operators';
8 GetFollowedCommunitiesResponse,
10 ListCommunitiesResponse,
27 WebSocketJsonResponse,
28 } from '../interfaces';
29 import { WebSocketService, UserService } from '../services';
30 import { PostListings } from './post-listings';
31 import { CommentNodes } from './comment-nodes';
32 import { SortSelect } from './sort-select';
33 import { ListingTypeSelect } from './listing-type-select';
34 import { DataTypeSelect } from './data-type-select';
35 import { SiteForm } from './site-form';
36 import { UserListing } from './user-listing';
37 import { CommunityLink } from './community-link';
44 getListingTypeFromProps,
51 createPostLikeFindRes,
56 import { i18n } from '../i18next';
57 import { T } from 'inferno-i18next';
60 subscribedCommunities: Array<CommunityUser>;
61 trendingCommunities: Array<Community>;
62 siteRes: GetSiteResponse;
63 showEditSite: boolean;
66 comments: Array<Comment>;
67 listingType: ListingType;
74 listingType: ListingType;
87 export class Main extends Component<any, MainState> {
88 private subscription: Subscription;
89 private emptyState: MainState = {
90 subscribedCommunities: [],
91 trendingCommunities: [],
99 number_of_users: null,
100 number_of_posts: null,
101 number_of_comments: null,
102 number_of_communities: null,
103 enable_downvotes: null,
104 open_registration: null,
115 listingType: getListingTypeFromProps(this.props),
116 dataType: getDataTypeFromProps(this.props),
117 sort: getSortTypeFromProps(this.props),
118 page: getPageFromProps(this.props),
121 constructor(props: any, context: any) {
122 super(props, context);
124 this.state = this.emptyState;
125 this.handleEditCancel = this.handleEditCancel.bind(this);
126 this.handleSortChange = this.handleSortChange.bind(this);
127 this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
128 this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
130 this.subscription = WebSocketService.Instance.subject
131 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
133 msg => this.parseMessage(msg),
134 err => console.error(err),
135 () => console.log('complete')
138 WebSocketService.Instance.getSite();
140 if (UserService.Instance.user) {
141 WebSocketService.Instance.getFollowedCommunities();
144 let listCommunitiesForm: ListCommunitiesForm = {
145 sort: SortType[SortType.Hot],
149 WebSocketService.Instance.listCommunities(listCommunitiesForm);
154 componentWillUnmount() {
155 this.subscription.unsubscribe();
158 static getDerivedStateFromProps(props: any): MainProps {
160 listingType: getListingTypeFromProps(props),
161 dataType: getDataTypeFromProps(props),
162 sort: getSortTypeFromProps(props),
163 page: getPageFromProps(props),
167 componentDidUpdate(_: any, lastState: MainState) {
169 lastState.listingType !== this.state.listingType ||
170 lastState.dataType !== this.state.dataType ||
171 lastState.sort !== this.state.sort ||
172 lastState.page !== this.state.page
174 this.setState({ loading: true });
181 <div class="container">
183 <main role="main" class="col-12 col-md-8">
186 <aside class="col-12 col-md-4">{this.my_sidebar()}</aside>
195 {!this.state.loading && (
197 <div class="card border-secondary mb-3">
198 <div class="card-body">
199 {this.trendingCommunities()}
200 {UserService.Instance.user &&
201 this.state.subscribedCommunities.length > 0 && (
204 <T i18nKey="subscribed_to_communities">
206 <Link class="text-body" to="/communities">
211 <ul class="list-inline">
212 {this.state.subscribedCommunities.map(community => (
213 <li class="list-inline-item">
216 name: community.community_name,
217 id: community.community_id,
218 local: community.community_local,
219 actor_id: community.community_actor_id,
228 class="btn btn-sm btn-secondary btn-block"
229 to="/create_community"
231 {i18n.t('create_a_community')}
243 trendingCommunities() {
247 <T i18nKey="trending_communities">
249 <Link class="text-body" to="/communities">
254 <ul class="list-inline">
255 {this.state.trendingCommunities.map(community => (
256 <li class="list-inline-item">
257 <CommunityLink community={community} />
268 {!this.state.showEditSite ? (
272 site={this.state.siteRes.site}
273 onCancel={this.handleEditCancel}
280 updateUrl(paramUpdates: UrlParams) {
281 const listingTypeStr =
282 paramUpdates.listingType ||
283 ListingType[this.state.listingType].toLowerCase();
285 paramUpdates.dataType || DataType[this.state.dataType].toLowerCase();
287 paramUpdates.sort || SortType[this.state.sort].toLowerCase();
288 const page = paramUpdates.page || this.state.page;
289 this.props.history.push(
290 `/home/data_type/${dataTypeStr}/listing_type/${listingTypeStr}/sort/${sortStr}/page/${page}`
297 <div class="card border-secondary mb-3">
298 <div class="card-body">
299 <h5 class="mb-0">{`${this.state.siteRes.site.name}`}</h5>
301 <ul class="list-inline mb-1 text-muted font-weight-bold">
302 <li className="list-inline-item-action">
305 onClick={linkEvent(this, this.handleEditClick)}
306 data-tippy-content={i18n.t('edit')}
308 <svg class="icon icon-inline">
309 <use xlinkHref="#icon-edit"></use>
315 <ul class="my-2 list-inline">
317 <li className="list-inline-item badge badge-secondary">
318 {i18n.t('number_online', { count: this.state.siteRes.online })}
321 <li className="list-inline-item badge badge-secondary">
322 {i18n.t('number_of_users', {
323 count: this.state.siteRes.site.number_of_users,
326 <li className="list-inline-item badge badge-secondary">
327 {i18n.t('number_of_communities', {
328 count: this.state.siteRes.site.number_of_communities,
331 <li className="list-inline-item badge badge-secondary">
332 {i18n.t('number_of_posts', {
333 count: this.state.siteRes.site.number_of_posts,
336 <li className="list-inline-item badge badge-secondary">
337 {i18n.t('number_of_comments', {
338 count: this.state.siteRes.site.number_of_comments,
341 <li className="list-inline-item">
342 <Link className="badge badge-secondary" to="/modlog">
347 <ul class="mt-1 list-inline small mb-0">
348 <li class="list-inline-item">{i18n.t('admins')}:</li>
349 {this.state.siteRes.admins.map(admin => (
350 <li class="list-inline-item">
354 avatar: admin.avatar,
356 actor_id: admin.actor_id,
365 {this.state.siteRes.site.description && (
366 <div class="card border-secondary mb-3">
367 <div class="card-body">
370 dangerouslySetInnerHTML={mdToHtml(
371 this.state.siteRes.site.description
383 <div class="card border-secondary">
384 <div class="card-body">
386 {i18n.t('powered_by')}
387 <svg class="icon mx-2">
388 <use xlinkHref="#icon-mouse">#</use>
395 <T i18nKey="landing_0">
397 <a href="https://en.wikipedia.org/wiki/Social_network_aggregation">
400 <a href="https://en.wikipedia.org/wiki/Fediverse">#</a>
401 <br class="big"></br>
405 <br class="big"></br>
406 <a href={repoUrl}>#</a>
407 <br class="big"></br>
408 <a href="https://www.rust-lang.org">#</a>
409 <a href="https://actix.rs/">#</a>
410 <a href="https://infernojs.org">#</a>
411 <a href="https://www.typescriptlang.org/">#</a>
412 <br class="big"></br>
413 <a href="https://github.com/LemmyNet/lemmy/graphs/contributors?type=a">
425 <div class="main-content-wrapper">
427 {this.state.loading ? (
429 <svg class="icon icon-spinner spin">
430 <use xlinkHref="#icon-spinner"></use>
444 return this.state.dataType == DataType.Post ? (
446 posts={this.state.posts}
449 sort={this.state.sort}
450 enableDownvotes={this.state.siteRes.site.enable_downvotes}
451 enableNsfw={this.state.siteRes.site.enable_nsfw}
455 nodes={commentsToFlatNodes(this.state.comments)}
458 sortType={this.state.sort}
460 enableDownvotes={this.state.siteRes.site.enable_downvotes}
467 <div className="mb-3">
470 type_={this.state.dataType}
471 onChange={this.handleDataTypeChange}
476 type_={this.state.listingType}
477 onChange={this.handleListingTypeChange}
481 <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
483 {this.state.listingType == ListingType.All && (
485 href={`/feeds/all.xml?sort=${SortType[this.state.sort]}`}
490 <svg class="icon text-muted small">
491 <use xlinkHref="#icon-rss">#</use>
495 {UserService.Instance.user &&
496 this.state.listingType == ListingType.Subscribed && (
498 href={`/feeds/front/${UserService.Instance.auth}.xml?sort=${
499 SortType[this.state.sort]
505 <svg class="icon text-muted small">
506 <use xlinkHref="#icon-rss">#</use>
517 {this.state.page > 1 && (
519 class="btn btn-sm btn-secondary mr-1"
520 onClick={linkEvent(this, this.prevPage)}
525 {this.state.posts.length > 0 && (
527 class="btn btn-sm btn-secondary"
528 onClick={linkEvent(this, this.nextPage)}
537 get canAdmin(): boolean {
539 UserService.Instance.user &&
540 this.state.siteRes.admins
542 .includes(UserService.Instance.user.id)
546 handleEditClick(i: Main) {
547 i.state.showEditSite = true;
552 this.state.showEditSite = false;
553 this.setState(this.state);
557 i.updateUrl({ page: i.state.page + 1 });
558 window.scrollTo(0, 0);
562 i.updateUrl({ page: i.state.page - 1 });
563 window.scrollTo(0, 0);
566 handleSortChange(val: SortType) {
567 this.updateUrl({ sort: SortType[val].toLowerCase(), page: 1 });
568 window.scrollTo(0, 0);
571 handleListingTypeChange(val: ListingType) {
572 this.updateUrl({ listingType: ListingType[val].toLowerCase(), page: 1 });
573 window.scrollTo(0, 0);
576 handleDataTypeChange(val: DataType) {
577 this.updateUrl({ dataType: DataType[val].toLowerCase(), page: 1 });
578 window.scrollTo(0, 0);
582 if (this.state.dataType == DataType.Post) {
583 let getPostsForm: GetPostsForm = {
584 page: this.state.page,
586 sort: SortType[this.state.sort],
587 type_: ListingType[this.state.listingType],
589 WebSocketService.Instance.getPosts(getPostsForm);
591 let getCommentsForm: GetCommentsForm = {
592 page: this.state.page,
594 sort: SortType[this.state.sort],
595 type_: ListingType[this.state.listingType],
597 WebSocketService.Instance.getComments(getCommentsForm);
601 parseMessage(msg: WebSocketJsonResponse) {
603 let res = wsJsonToRes(msg);
605 toast(i18n.t(msg.error), 'danger');
607 } else if (msg.reconnect) {
609 } else if (res.op == UserOperation.GetFollowedCommunities) {
610 let data = res.data as GetFollowedCommunitiesResponse;
611 this.state.subscribedCommunities = data.communities;
612 this.setState(this.state);
613 } else if (res.op == UserOperation.ListCommunities) {
614 let data = res.data as ListCommunitiesResponse;
615 this.state.trendingCommunities = data.communities;
616 this.setState(this.state);
617 } else if (res.op == UserOperation.GetSite) {
618 let data = res.data as GetSiteResponse;
620 // This means it hasn't been set up yet
622 this.context.router.history.push('/setup');
624 this.state.siteRes.admins = data.admins;
625 this.state.siteRes.site = data.site;
626 this.state.siteRes.banned = data.banned;
627 this.state.siteRes.online = data.online;
628 this.setState(this.state);
629 document.title = `${this.state.siteRes.site.name}`;
630 } else if (res.op == UserOperation.EditSite) {
631 let data = res.data as SiteResponse;
632 this.state.siteRes.site = data.site;
633 this.state.showEditSite = false;
634 this.setState(this.state);
635 toast(i18n.t('site_saved'));
636 } else if (res.op == UserOperation.GetPosts) {
637 let data = res.data as GetPostsResponse;
638 this.state.posts = data.posts;
639 this.state.loading = false;
640 this.setState(this.state);
642 } else if (res.op == UserOperation.CreatePost) {
643 let data = res.data as PostResponse;
645 // If you're on subscribed, only push it if you're subscribed.
646 if (this.state.listingType == ListingType.Subscribed) {
648 this.state.subscribedCommunities
649 .map(c => c.community_id)
650 .includes(data.post.community_id)
652 this.state.posts.unshift(data.post);
656 let nsfw = data.post.nsfw || data.post.community_nsfw;
658 // Don't push the post if its nsfw, and don't have that setting on
662 UserService.Instance.user &&
663 UserService.Instance.user.show_nsfw)
665 this.state.posts.unshift(data.post);
668 this.setState(this.state);
669 } else if (res.op == UserOperation.EditPost) {
670 let data = res.data as PostResponse;
671 editPostFindRes(data, this.state.posts);
672 this.setState(this.state);
673 } else if (res.op == UserOperation.CreatePostLike) {
674 let data = res.data as PostResponse;
675 createPostLikeFindRes(data, this.state.posts);
676 this.setState(this.state);
677 } else if (res.op == UserOperation.AddAdmin) {
678 let data = res.data as AddAdminResponse;
679 this.state.siteRes.admins = data.admins;
680 this.setState(this.state);
681 } else if (res.op == UserOperation.BanUser) {
682 let data = res.data as BanUserResponse;
683 let found = this.state.siteRes.banned.find(u => (u.id = data.user.id));
685 // Remove the banned if its found in the list, and the action is an unban
686 if (found && !data.banned) {
687 this.state.siteRes.banned = this.state.siteRes.banned.filter(
688 i => i.id !== data.user.id
691 this.state.siteRes.banned.push(data.user);
695 .filter(p => p.creator_id == data.user.id)
696 .forEach(p => (p.banned = data.banned));
698 this.setState(this.state);
699 } else if (res.op == UserOperation.GetComments) {
700 let data = res.data as GetCommentsResponse;
701 this.state.comments = data.comments;
702 this.state.loading = false;
703 this.setState(this.state);
704 } else if (res.op == UserOperation.EditComment) {
705 let data = res.data as CommentResponse;
706 editCommentRes(data, this.state.comments);
707 this.setState(this.state);
708 } else if (res.op == UserOperation.CreateComment) {
709 let data = res.data as CommentResponse;
711 // Necessary since it might be a user reply
712 if (data.recipient_ids.length == 0) {
713 // If you're on subscribed, only push it if you're subscribed.
714 if (this.state.listingType == ListingType.Subscribed) {
716 this.state.subscribedCommunities
717 .map(c => c.community_id)
718 .includes(data.comment.community_id)
720 this.state.comments.unshift(data.comment);
723 this.state.comments.unshift(data.comment);
725 this.setState(this.state);
727 } else if (res.op == UserOperation.SaveComment) {
728 let data = res.data as CommentResponse;
729 saveCommentRes(data, this.state.comments);
730 this.setState(this.state);
731 } else if (res.op == UserOperation.CreateCommentLike) {
732 let data = res.data as CommentResponse;
733 createCommentLikeRes(data, this.state.comments);
734 this.setState(this.state);