1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { WebSocketService, UserService } from '../services';
17 AddModToCommunityForm,
20 TransferCommunityForm,
21 } from '../interfaces';
22 import { MomentTime } from './moment-time';
23 import { PostForm } from './post-form';
24 import { IFramelyCard } from './iframely-card';
25 import { UserListing } from './user-listing';
26 import { CommunityLink } from './community-link';
40 import { i18n } from '../i18next';
42 interface PostListingState {
44 showRemoveDialog: boolean;
46 showBanDialog: boolean;
50 showConfirmTransferSite: boolean;
51 showConfirmTransferCommunity: boolean;
52 imageExpanded: boolean;
54 showAdvanced: boolean;
61 interface PostListingProps {
63 showCommunity?: boolean;
65 moderators?: Array<CommunityUser>;
66 admins?: Array<UserView>;
67 enableDownvotes: boolean;
71 export class PostListing extends Component<PostListingProps, PostListingState> {
72 private emptyState: PostListingState = {
74 showRemoveDialog: false,
79 banType: BanType.Community,
80 showConfirmTransferSite: false,
81 showConfirmTransferCommunity: false,
85 my_vote: this.props.post.my_vote,
86 score: this.props.post.score,
87 upvotes: this.props.post.upvotes,
88 downvotes: this.props.post.downvotes,
91 constructor(props: any, context: any) {
92 super(props, context);
94 this.state = this.emptyState;
95 this.handlePostLike = this.handlePostLike.bind(this);
96 this.handlePostDisLike = this.handlePostDisLike.bind(this);
97 this.handleEditPost = this.handleEditPost.bind(this);
98 this.handleEditCancel = this.handleEditCancel.bind(this);
101 componentWillReceiveProps(nextProps: PostListingProps) {
102 this.state.my_vote = nextProps.post.my_vote;
103 this.state.upvotes = nextProps.post.upvotes;
104 this.state.downvotes = nextProps.post.downvotes;
105 this.state.score = nextProps.post.score;
106 if (this.props.post.id !== nextProps.post.id) {
107 this.state.imageExpanded = false;
109 this.setState(this.state);
115 {!this.state.showEdit ? (
123 post={this.props.post}
124 onEdit={this.handleEditPost}
125 onCancel={this.handleEditCancel}
126 enableNsfw={this.props.enableNsfw}
127 enableDownvotes={this.props.enableDownvotes}
139 {this.props.post.url &&
140 this.props.showBody &&
141 this.props.post.embed_title && (
142 <IFramelyCard post={this.props.post} />
144 {this.props.showBody && this.props.post.body && (
146 {this.state.viewSource ? (
147 <pre>{this.props.post.body}</pre>
151 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
161 imgThumb(src: string) {
162 let post = this.props.post;
165 className={`img-fluid thumbnail rounded ${
166 post.nsfw || post.community_nsfw ? 'img-blur' : ''
173 getImage(thumbnail: boolean = false) {
174 let post = this.props.post;
175 if (isImage(post.url)) {
176 if (post.url.includes('pictrs')) {
177 return pictrsImage(post.url, thumbnail);
178 } else if (post.thumbnail_url) {
179 return pictrsImage(post.thumbnail_url, thumbnail);
183 } else if (post.thumbnail_url) {
184 return pictrsImage(post.thumbnail_url, thumbnail);
189 let post = this.props.post;
191 if (isImage(post.url)) {
194 class="text-body pointer d-inline-block position-relative"
195 data-tippy-content={i18n.t('expand_here')}
196 onClick={linkEvent(this, this.handleImageExpandClick)}
198 {this.imgThumb(this.getImage(true))}
199 <svg class="icon mini-overlay">
200 <use xlinkHref="#icon-image"></use>
204 } else if (post.thumbnail_url) {
207 class="text-body d-inline-block position-relative"
213 {this.imgThumb(this.getImage(true))}
214 <svg class="icon mini-overlay">
215 <use xlinkHref="#icon-external-link"></use>
219 } else if (post.url) {
220 if (isVideo(post.url)) {
222 <div class="embed-responsive embed-responsive-16by9">
228 class="embed-responsive-item"
230 <source src={post.url} type="video/mp4" />
237 className="text-body"
243 <div class="thumbnail rounded bg-light d-flex justify-content-center">
244 <svg class="icon d-flex align-items-center">
245 <use xlinkHref="#icon-external-link"></use>
254 className="text-body"
255 to={`/post/${post.id}`}
256 title={i18n.t('comments')}
258 <div class="thumbnail rounded bg-light d-flex justify-content-center">
259 <svg class="icon d-flex align-items-center">
260 <use xlinkHref="#icon-message-square"></use>
269 let post = this.props.post;
271 <ul class="list-inline mb-1 text-muted small">
272 <li className="list-inline-item">
275 name: post.creator_name,
276 preferred_username: post.creator_preferred_username,
277 avatar: post.creator_avatar,
279 local: post.creator_local,
280 actor_id: post.creator_actor_id,
281 published: post.creator_published,
286 <span className="mx-1 badge badge-light">{i18n.t('mod')}</span>
289 <span className="mx-1 badge badge-light">{i18n.t('admin')}</span>
291 {(post.banned_from_community || post.banned) && (
292 <span className="mx-1 badge badge-danger">{i18n.t('banned')}</span>
294 {this.props.showCommunity && (
296 <span class="mx-1"> {i18n.t('to')} </span>
299 name: post.community_name,
300 id: post.community_id,
301 local: post.community_local,
302 actor_id: post.community_actor_id,
303 icon: post.community_icon,
309 <li className="list-inline-item">•</li>
310 {post.url && !(hostname(post.url) == window.location.hostname) && (
312 <li className="list-inline-item">
314 className="text-muted font-italic"
323 <li className="list-inline-item">•</li>
326 <li className="list-inline-item">
328 <MomentTime data={post} />
333 <li className="list-inline-item">•</li>
334 <li className="list-inline-item">
335 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
337 className="text-muted"
338 data-tippy-content={md.render(previewLines(post.body))}
339 data-tippy-allowHtml={true}
340 to={`/post/${post.id}`}
342 <svg class="mr-1 icon icon-inline">
343 <use xlinkHref="#icon-book-open"></use>
355 <div className={`vote-bar col-1 pr-0 small text-center`}>
357 className={`btn-animate btn btn-link p-0 ${
358 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
360 onClick={linkEvent(this, this.handlePostLike)}
361 data-tippy-content={i18n.t('upvote')}
363 <svg class="icon upvote">
364 <use xlinkHref="#icon-arrow-up1"></use>
368 class={`unselectable pointer font-weight-bold text-muted px-1`}
369 data-tippy-content={this.pointsTippy}
373 {this.props.enableDownvotes && (
375 className={`btn-animate btn btn-link p-0 ${
376 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
378 onClick={linkEvent(this, this.handlePostDisLike)}
379 data-tippy-content={i18n.t('downvote')}
381 <svg class="icon downvote">
382 <use xlinkHref="#icon-arrow-down1"></use>
391 let post = this.props.post;
393 <div className="post-title overflow-hidden">
395 {this.props.showBody && post.url ? (
397 className={!post.stickied ? 'text-body' : 'text-primary'}
407 className={!post.stickied ? 'text-body' : 'text-primary'}
408 to={`/post/${post.id}`}
409 title={i18n.t('comments')}
414 {(isImage(post.url) || this.props.post.thumbnail_url) && (
416 {!this.state.imageExpanded ? (
418 class="text-monospace unselectable pointer ml-2 text-muted small"
419 data-tippy-content={i18n.t('expand_here')}
420 onClick={linkEvent(this, this.handleImageExpandClick)}
422 <svg class="icon icon-inline">
423 <use xlinkHref="#icon-plus-square"></use>
429 class="text-monospace unselectable pointer ml-2 text-muted small"
430 onClick={linkEvent(this, this.handleImageExpandClick)}
432 <svg class="icon icon-inline">
433 <use xlinkHref="#icon-minus-square"></use>
439 onClick={linkEvent(this, this.handleImageExpandClick)}
442 class="img-fluid img-expanded"
443 src={this.getImage()}
452 <small className="ml-2 text-muted font-italic">
458 className="unselectable pointer ml-2 text-muted font-italic"
459 data-tippy-content={i18n.t('deleted')}
461 <svg class={`icon icon-inline text-danger`}>
462 <use xlinkHref="#icon-trash"></use>
468 className="unselectable pointer ml-2 text-muted font-italic"
469 data-tippy-content={i18n.t('locked')}
471 <svg class={`icon icon-inline text-danger`}>
472 <use xlinkHref="#icon-lock"></use>
478 className="unselectable pointer ml-2 text-muted font-italic"
479 data-tippy-content={i18n.t('stickied')}
481 <svg class={`icon icon-inline text-primary`}>
482 <use xlinkHref="#icon-pin"></use>
487 <small className="ml-2 text-muted font-italic">
496 commentsLine(showVotes: boolean = false) {
497 let post = this.props.post;
499 <ul class="d-flex align-items-center list-inline mb-1 text-muted small">
500 <li className="list-inline-item">
502 className="text-muted"
503 title={i18n.t('number_of_comments', {
504 count: post.number_of_comments,
506 to={`/post/${post.id}`}
508 <svg class="mr-1 icon icon-inline">
509 <use xlinkHref="#icon-message-square"></use>
511 {i18n.t('number_of_comments', {
512 count: post.number_of_comments,
516 {(showVotes || this.state.upvotes !== this.state.score) && (
518 <li className="list-inline-item">•</li>
520 class="unselectable pointer mr-2"
521 data-tippy-content={this.pointsTippy}
523 <li className="list-inline-item">
525 className={`btn-animate btn btn-link p-0 ${
526 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
528 onClick={linkEvent(this, this.handlePostLike)}
530 <svg class="small icon icon-inline mr-1">
531 <use xlinkHref="#icon-arrow-up"></use>
536 <li className="list-inline-item">
538 className={`btn-animate btn btn-link p-0 ${
539 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
541 onClick={linkEvent(this, this.handlePostDisLike)}
543 <svg class="small icon icon-inline mr-1">
544 <use xlinkHref="#icon-arrow-down"></use>
546 {this.state.downvotes}
558 this.props.post.duplicates && (
559 <ul class="list-inline mb-1 small text-muted">
561 <li className="list-inline-item mr-2">
562 {i18n.t('cross_posted_to')}
564 {this.props.post.duplicates.map(post => (
565 <li className="list-inline-item mr-2">
566 <Link to={`/post/${post.id}`}>{post.community_name}</Link>
576 let post = this.props.post;
578 <ul class="list-inline mb-1 text-muted font-weight-bold">
579 {UserService.Instance.user && (
581 {this.props.showBody && (
583 <li className="list-inline-item">
585 class="btn btn-link btn-animate text-muted"
586 onClick={linkEvent(this, this.handleSavePostClick)}
588 post.saved ? i18n.t('unsave') : i18n.t('save')
592 class={`icon icon-inline ${post.saved && 'text-warning'}`}
594 <use xlinkHref="#icon-star"></use>
598 <li className="list-inline-item">
600 class="btn btn-link btn-animate text-muted"
601 to={`/create_post${this.crossPostParams}`}
602 title={i18n.t('cross_post')}
604 <svg class="icon icon-inline">
605 <use xlinkHref="#icon-copy"></use>
611 {this.myPost && this.props.showBody && (
613 <li className="list-inline-item">
615 class="btn btn-link btn-animate text-muted"
616 onClick={linkEvent(this, this.handleEditClick)}
617 data-tippy-content={i18n.t('edit')}
619 <svg class="icon icon-inline">
620 <use xlinkHref="#icon-edit"></use>
624 <li className="list-inline-item">
626 class="btn btn-link btn-animate text-muted"
627 onClick={linkEvent(this, this.handleDeleteClick)}
629 !post.deleted ? i18n.t('delete') : i18n.t('restore')
633 class={`icon icon-inline ${
634 post.deleted && 'text-danger'
637 <use xlinkHref="#icon-trash"></use>
644 {!this.state.showAdvanced && this.props.showBody ? (
645 <li className="list-inline-item">
647 class="btn btn-link btn-animate text-muted"
648 onClick={linkEvent(this, this.handleShowAdvanced)}
649 data-tippy-content={i18n.t('more')}
651 <svg class="icon icon-inline">
652 <use xlinkHref="#icon-more-vertical"></use>
658 {this.props.showBody && post.body && (
659 <li className="list-inline-item">
661 class="btn btn-link btn-animate text-muted"
662 onClick={linkEvent(this, this.handleViewSource)}
663 data-tippy-content={i18n.t('view_source')}
666 class={`icon icon-inline ${
667 this.state.viewSource && 'text-success'
670 <use xlinkHref="#icon-file-text"></use>
675 {this.canModOnSelf && (
677 <li className="list-inline-item">
679 class="btn btn-link btn-animate text-muted"
680 onClick={linkEvent(this, this.handleModLock)}
682 post.locked ? i18n.t('unlock') : i18n.t('lock')
686 class={`icon icon-inline ${
687 post.locked && 'text-danger'
690 <use xlinkHref="#icon-lock"></use>
694 <li className="list-inline-item">
696 class="btn btn-link btn-animate text-muted"
697 onClick={linkEvent(this, this.handleModSticky)}
699 post.stickied ? i18n.t('unsticky') : i18n.t('sticky')
703 class={`icon icon-inline ${
704 post.stickied && 'text-success'
707 <use xlinkHref="#icon-pin"></use>
713 {/* Mods can ban from community, and appoint as mods to community */}
714 {(this.canMod || this.canAdmin) && (
715 <li className="list-inline-item">
719 onClick={linkEvent(this, this.handleModRemoveShow)}
726 onClick={linkEvent(this, this.handleModRemoveSubmit)}
736 <li className="list-inline-item">
737 {!post.banned_from_community ? (
742 this.handleModBanFromCommunityShow
752 this.handleModBanFromCommunitySubmit
760 {!post.banned_from_community && (
761 <li className="list-inline-item">
766 this.handleAddModToCommunity
770 ? i18n.t('remove_as_mod')
771 : i18n.t('appoint_as_mod')}
777 {/* Community creators and admins can transfer community to another mod */}
778 {(this.amCommunityCreator || this.canAdmin) && this.isMod && (
779 <li className="list-inline-item">
780 {!this.state.showConfirmTransferCommunity ? (
785 this.handleShowConfirmTransferCommunity
788 {i18n.t('transfer_community')}
792 <span class="d-inline-block mr-1">
793 {i18n.t('are_you_sure')}
796 class="pointer d-inline-block mr-1"
799 this.handleTransferCommunity
805 class="pointer d-inline-block"
808 this.handleCancelShowConfirmTransferCommunity
817 {/* Admins can ban from all, and appoint other admins */}
821 <li className="list-inline-item">
825 onClick={linkEvent(this, this.handleModBanShow)}
827 {i18n.t('ban_from_site')}
832 onClick={linkEvent(this, this.handleModBanSubmit)}
834 {i18n.t('unban_from_site')}
840 <li className="list-inline-item">
843 onClick={linkEvent(this, this.handleAddAdmin)}
846 ? i18n.t('remove_as_admin')
847 : i18n.t('appoint_as_admin')}
853 {/* Site Creator can transfer to another admin */}
854 {this.amSiteCreator && this.isAdmin && (
855 <li className="list-inline-item">
856 {!this.state.showConfirmTransferSite ? (
861 this.handleShowConfirmTransferSite
864 {i18n.t('transfer_site')}
868 <span class="d-inline-block mr-1">
869 {i18n.t('are_you_sure')}
872 class="pointer d-inline-block mr-1"
873 onClick={linkEvent(this, this.handleTransferSite)}
878 class="pointer d-inline-block"
881 this.handleCancelShowConfirmTransferSite
898 removeAndBanDialogs() {
899 let post = this.props.post;
902 {this.state.showRemoveDialog && (
905 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
909 class="form-control mr-2"
910 placeholder={i18n.t('reason')}
911 value={this.state.removeReason}
912 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
914 <button type="submit" class="btn btn-secondary">
915 {i18n.t('remove_post')}
919 {this.state.showBanDialog && (
920 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
921 <div class="form-group row">
922 <label class="col-form-label" htmlFor="post-listing-reason">
927 id="post-listing-reason"
928 class="form-control mr-2"
929 placeholder={i18n.t('reason')}
930 value={this.state.banReason}
931 onInput={linkEvent(this, this.handleModBanReasonChange)}
934 {/* TODO hold off on expires until later */}
935 {/* <div class="form-group row"> */}
936 {/* <label class="col-form-label">Expires</label> */}
937 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
939 <div class="form-group row">
940 <button type="submit" class="btn btn-secondary">
941 {i18n.t('ban')} {post.creator_name}
951 return this.props.post.thumbnail_url || isImage(this.props.post.url) ? (
953 <div class="col-8">{this.postTitleLine()}</div>
955 {/* Post body prev or thumbnail */}
956 {!this.state.imageExpanded && this.thumbnail()}
964 showMobilePreview() {
966 this.props.post.body &&
967 !this.props.showBody && (
969 className="md-div mb-1"
970 dangerouslySetInnerHTML={{
971 __html: md.render(previewLines(this.props.post.body)),
981 {/* The mobile view*/}
982 <div class="d-block d-sm-none">
987 {/* If it has a thumbnail, do a right aligned thumbnail */}
988 {this.mobileThumbnail()}
990 {/* Show a preview of the post body */}
991 {this.showMobilePreview()}
993 {this.commentsLine(true)}
994 {this.duplicatesLine()}
996 {this.removeAndBanDialogs()}
1001 {/* The larger view*/}
1002 <div class="d-none d-sm-block">
1005 {!this.state.imageExpanded && (
1006 <div class="col-sm-2 pr-0">
1007 <div class="">{this.thumbnail()}</div>
1012 this.state.imageExpanded ? 'col-12' : 'col-12 col-sm-9'
1016 <div className="col-12">
1017 {this.postTitleLine()}
1018 {this.createdLine()}
1019 {this.commentsLine()}
1020 {this.duplicatesLine()}
1021 {this.postActions()}
1022 {this.removeAndBanDialogs()}
1032 private get myPost(): boolean {
1034 UserService.Instance.user &&
1035 this.props.post.creator_id == UserService.Instance.user.id
1039 get isMod(): boolean {
1041 this.props.moderators &&
1043 this.props.moderators.map(m => m.user_id),
1044 this.props.post.creator_id
1049 get isAdmin(): boolean {
1051 this.props.admins &&
1053 this.props.admins.map(a => a.id),
1054 this.props.post.creator_id
1059 get canMod(): boolean {
1060 if (this.props.admins && this.props.moderators) {
1061 let adminsThenMods = this.props.admins
1063 .concat(this.props.moderators.map(m => m.user_id));
1066 UserService.Instance.user,
1068 this.props.post.creator_id
1075 get canModOnSelf(): boolean {
1076 if (this.props.admins && this.props.moderators) {
1077 let adminsThenMods = this.props.admins
1079 .concat(this.props.moderators.map(m => m.user_id));
1082 UserService.Instance.user,
1084 this.props.post.creator_id,
1092 get canAdmin(): boolean {
1094 this.props.admins &&
1096 UserService.Instance.user,
1097 this.props.admins.map(a => a.id),
1098 this.props.post.creator_id
1103 get amCommunityCreator(): boolean {
1105 this.props.moderators &&
1106 UserService.Instance.user &&
1107 this.props.post.creator_id != UserService.Instance.user.id &&
1108 UserService.Instance.user.id == this.props.moderators[0].user_id
1112 get amSiteCreator(): boolean {
1114 this.props.admins &&
1115 UserService.Instance.user &&
1116 this.props.post.creator_id != UserService.Instance.user.id &&
1117 UserService.Instance.user.id == this.props.admins[0].id
1121 handlePostLike(i: PostListing) {
1122 if (!UserService.Instance.user) {
1123 this.context.router.history.push(`/login`);
1126 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1128 if (i.state.my_vote == 1) {
1131 } else if (i.state.my_vote == -1) {
1132 i.state.downvotes--;
1140 i.state.my_vote = new_vote;
1142 let form: CreatePostLikeForm = {
1143 post_id: i.props.post.id,
1144 score: i.state.my_vote,
1147 WebSocketService.Instance.likePost(form);
1148 i.setState(i.state);
1152 handlePostDisLike(i: PostListing) {
1153 if (!UserService.Instance.user) {
1154 this.context.router.history.push(`/login`);
1157 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1159 if (i.state.my_vote == 1) {
1162 i.state.downvotes++;
1163 } else if (i.state.my_vote == -1) {
1164 i.state.downvotes--;
1167 i.state.downvotes++;
1171 i.state.my_vote = new_vote;
1173 let form: CreatePostLikeForm = {
1174 post_id: i.props.post.id,
1175 score: i.state.my_vote,
1178 WebSocketService.Instance.likePost(form);
1179 i.setState(i.state);
1183 handleEditClick(i: PostListing) {
1184 i.state.showEdit = true;
1185 i.setState(i.state);
1188 handleEditCancel() {
1189 this.state.showEdit = false;
1190 this.setState(this.state);
1193 // The actual editing is done in the recieve for post
1195 this.state.showEdit = false;
1196 this.setState(this.state);
1199 handleDeleteClick(i: PostListing) {
1200 let deleteForm: DeletePostForm = {
1201 edit_id: i.props.post.id,
1202 deleted: !i.props.post.deleted,
1205 WebSocketService.Instance.deletePost(deleteForm);
1208 handleSavePostClick(i: PostListing) {
1209 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1210 let form: SavePostForm = {
1211 post_id: i.props.post.id,
1215 WebSocketService.Instance.savePost(form);
1218 get crossPostParams(): string {
1219 let params = `?title=${this.props.post.name}`;
1220 let post = this.props.post;
1223 params += `&url=${post.url}`;
1225 if (this.props.post.body) {
1226 params += `&body=${this.props.post.body}`;
1231 handleModRemoveShow(i: PostListing) {
1232 i.state.showRemoveDialog = true;
1233 i.setState(i.state);
1236 handleModRemoveReasonChange(i: PostListing, event: any) {
1237 i.state.removeReason = event.target.value;
1238 i.setState(i.state);
1241 handleModRemoveSubmit(i: PostListing) {
1242 event.preventDefault();
1243 let form: RemovePostForm = {
1244 edit_id: i.props.post.id,
1245 removed: !i.props.post.removed,
1246 reason: i.state.removeReason,
1249 WebSocketService.Instance.removePost(form);
1251 i.state.showRemoveDialog = false;
1252 i.setState(i.state);
1255 handleModLock(i: PostListing) {
1256 let form: LockPostForm = {
1257 edit_id: i.props.post.id,
1258 locked: !i.props.post.locked,
1261 WebSocketService.Instance.lockPost(form);
1264 handleModSticky(i: PostListing) {
1265 let form: StickyPostForm = {
1266 edit_id: i.props.post.id,
1267 stickied: !i.props.post.stickied,
1270 WebSocketService.Instance.stickyPost(form);
1273 handleModBanFromCommunityShow(i: PostListing) {
1274 i.state.showBanDialog = true;
1275 i.state.banType = BanType.Community;
1276 i.setState(i.state);
1279 handleModBanShow(i: PostListing) {
1280 i.state.showBanDialog = true;
1281 i.state.banType = BanType.Site;
1282 i.setState(i.state);
1285 handleModBanReasonChange(i: PostListing, event: any) {
1286 i.state.banReason = event.target.value;
1287 i.setState(i.state);
1290 handleModBanExpiresChange(i: PostListing, event: any) {
1291 i.state.banExpires = event.target.value;
1292 i.setState(i.state);
1295 handleModBanFromCommunitySubmit(i: PostListing) {
1296 i.state.banType = BanType.Community;
1297 i.setState(i.state);
1298 i.handleModBanBothSubmit(i);
1301 handleModBanSubmit(i: PostListing) {
1302 i.state.banType = BanType.Site;
1303 i.setState(i.state);
1304 i.handleModBanBothSubmit(i);
1307 handleModBanBothSubmit(i: PostListing) {
1308 event.preventDefault();
1310 if (i.state.banType == BanType.Community) {
1311 let form: BanFromCommunityForm = {
1312 user_id: i.props.post.creator_id,
1313 community_id: i.props.post.community_id,
1314 ban: !i.props.post.banned_from_community,
1315 reason: i.state.banReason,
1316 expires: getUnixTime(i.state.banExpires),
1318 WebSocketService.Instance.banFromCommunity(form);
1320 let form: BanUserForm = {
1321 user_id: i.props.post.creator_id,
1322 ban: !i.props.post.banned,
1323 reason: i.state.banReason,
1324 expires: getUnixTime(i.state.banExpires),
1326 WebSocketService.Instance.banUser(form);
1329 i.state.showBanDialog = false;
1330 i.setState(i.state);
1333 handleAddModToCommunity(i: PostListing) {
1334 let form: AddModToCommunityForm = {
1335 user_id: i.props.post.creator_id,
1336 community_id: i.props.post.community_id,
1339 WebSocketService.Instance.addModToCommunity(form);
1340 i.setState(i.state);
1343 handleAddAdmin(i: PostListing) {
1344 let form: AddAdminForm = {
1345 user_id: i.props.post.creator_id,
1348 WebSocketService.Instance.addAdmin(form);
1349 i.setState(i.state);
1352 handleShowConfirmTransferCommunity(i: PostListing) {
1353 i.state.showConfirmTransferCommunity = true;
1354 i.setState(i.state);
1357 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1358 i.state.showConfirmTransferCommunity = false;
1359 i.setState(i.state);
1362 handleTransferCommunity(i: PostListing) {
1363 let form: TransferCommunityForm = {
1364 community_id: i.props.post.community_id,
1365 user_id: i.props.post.creator_id,
1367 WebSocketService.Instance.transferCommunity(form);
1368 i.state.showConfirmTransferCommunity = false;
1369 i.setState(i.state);
1372 handleShowConfirmTransferSite(i: PostListing) {
1373 i.state.showConfirmTransferSite = true;
1374 i.setState(i.state);
1377 handleCancelShowConfirmTransferSite(i: PostListing) {
1378 i.state.showConfirmTransferSite = false;
1379 i.setState(i.state);
1382 handleTransferSite(i: PostListing) {
1383 let form: TransferSiteForm = {
1384 user_id: i.props.post.creator_id,
1386 WebSocketService.Instance.transferSite(form);
1387 i.state.showConfirmTransferSite = false;
1388 i.setState(i.state);
1391 handleImageExpandClick(i: PostListing) {
1392 i.state.imageExpanded = !i.state.imageExpanded;
1393 i.setState(i.state);
1396 handleViewSource(i: PostListing) {
1397 i.state.viewSource = !i.state.viewSource;
1398 i.setState(i.state);
1401 handleShowAdvanced(i: PostListing) {
1402 i.state.showAdvanced = !i.state.showAdvanced;
1403 i.setState(i.state);
1407 get pointsTippy(): string {
1408 let points = i18n.t('number_of_points', {
1409 count: this.state.score,
1412 let upvotes = i18n.t('number_of_upvotes', {
1413 count: this.state.upvotes,
1416 let downvotes = i18n.t('number_of_downvotes', {
1417 count: this.state.downvotes,
1420 return `${points} • ${upvotes} • ${downvotes}`;