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="float-right 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="float-right 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 && post.creator_local && (
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) &&
780 post.creator_local && (
781 <li className="list-inline-item">
782 {!this.state.showConfirmTransferCommunity ? (
787 this.handleShowConfirmTransferCommunity
790 {i18n.t('transfer_community')}
794 <span class="d-inline-block mr-1">
795 {i18n.t('are_you_sure')}
798 class="pointer d-inline-block mr-1"
801 this.handleTransferCommunity
807 class="pointer d-inline-block"
810 this.handleCancelShowConfirmTransferCommunity
819 {/* Admins can ban from all, and appoint other admins */}
823 <li className="list-inline-item">
827 onClick={linkEvent(this, this.handleModBanShow)}
829 {i18n.t('ban_from_site')}
834 onClick={linkEvent(this, this.handleModBanSubmit)}
836 {i18n.t('unban_from_site')}
841 {!post.banned && post.creator_local && (
842 <li className="list-inline-item">
845 onClick={linkEvent(this, this.handleAddAdmin)}
848 ? i18n.t('remove_as_admin')
849 : i18n.t('appoint_as_admin')}
855 {/* Site Creator can transfer to another admin */}
856 {this.amSiteCreator && this.isAdmin && (
857 <li className="list-inline-item">
858 {!this.state.showConfirmTransferSite ? (
863 this.handleShowConfirmTransferSite
866 {i18n.t('transfer_site')}
870 <span class="d-inline-block mr-1">
871 {i18n.t('are_you_sure')}
874 class="pointer d-inline-block mr-1"
875 onClick={linkEvent(this, this.handleTransferSite)}
880 class="pointer d-inline-block"
883 this.handleCancelShowConfirmTransferSite
900 removeAndBanDialogs() {
901 let post = this.props.post;
904 {this.state.showRemoveDialog && (
907 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
911 class="form-control mr-2"
912 placeholder={i18n.t('reason')}
913 value={this.state.removeReason}
914 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
916 <button type="submit" class="btn btn-secondary">
917 {i18n.t('remove_post')}
921 {this.state.showBanDialog && (
922 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
923 <div class="form-group row">
924 <label class="col-form-label" htmlFor="post-listing-reason">
929 id="post-listing-reason"
930 class="form-control mr-2"
931 placeholder={i18n.t('reason')}
932 value={this.state.banReason}
933 onInput={linkEvent(this, this.handleModBanReasonChange)}
936 {/* TODO hold off on expires until later */}
937 {/* <div class="form-group row"> */}
938 {/* <label class="col-form-label">Expires</label> */}
939 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
941 <div class="form-group row">
942 <button type="submit" class="btn btn-secondary">
943 {i18n.t('ban')} {post.creator_name}
953 return this.props.post.thumbnail_url || isImage(this.props.post.url) ? (
955 <div className={`${this.state.imageExpanded ? 'col-12' : 'col-8'}`}>
956 {this.postTitleLine()}
959 {/* Post body prev or thumbnail */}
960 {!this.state.imageExpanded && this.thumbnail()}
968 showMobilePreview() {
970 this.props.post.body &&
971 !this.props.showBody && (
973 className="md-div mb-1"
974 dangerouslySetInnerHTML={{
975 __html: md.render(previewLines(this.props.post.body)),
985 {/* The mobile view*/}
986 <div class="d-block d-sm-none">
991 {/* If it has a thumbnail, do a right aligned thumbnail */}
992 {this.mobileThumbnail()}
994 {/* Show a preview of the post body */}
995 {this.showMobilePreview()}
997 {this.commentsLine(true)}
998 {this.duplicatesLine()}
1000 {this.removeAndBanDialogs()}
1005 {/* The larger view*/}
1006 <div class="d-none d-sm-block">
1009 {!this.state.imageExpanded && (
1010 <div class="col-sm-2 pr-0">
1011 <div class="">{this.thumbnail()}</div>
1016 this.state.imageExpanded ? 'col-12' : 'col-12 col-sm-9'
1020 <div className="col-12">
1021 {this.postTitleLine()}
1022 {this.createdLine()}
1023 {this.commentsLine()}
1024 {this.duplicatesLine()}
1025 {this.postActions()}
1026 {this.removeAndBanDialogs()}
1036 private get myPost(): boolean {
1038 UserService.Instance.user &&
1039 this.props.post.creator_id == UserService.Instance.user.id
1043 get isMod(): boolean {
1045 this.props.moderators &&
1047 this.props.moderators.map(m => m.user_id),
1048 this.props.post.creator_id
1053 get isAdmin(): boolean {
1055 this.props.admins &&
1057 this.props.admins.map(a => a.id),
1058 this.props.post.creator_id
1063 get canMod(): boolean {
1064 if (this.props.admins && this.props.moderators) {
1065 let adminsThenMods = this.props.admins
1067 .concat(this.props.moderators.map(m => m.user_id));
1070 UserService.Instance.user,
1072 this.props.post.creator_id
1079 get canModOnSelf(): boolean {
1080 if (this.props.admins && this.props.moderators) {
1081 let adminsThenMods = this.props.admins
1083 .concat(this.props.moderators.map(m => m.user_id));
1086 UserService.Instance.user,
1088 this.props.post.creator_id,
1096 get canAdmin(): boolean {
1098 this.props.admins &&
1100 UserService.Instance.user,
1101 this.props.admins.map(a => a.id),
1102 this.props.post.creator_id
1107 get amCommunityCreator(): boolean {
1109 this.props.moderators &&
1110 UserService.Instance.user &&
1111 this.props.post.creator_id != UserService.Instance.user.id &&
1112 UserService.Instance.user.id == this.props.moderators[0].user_id
1116 get amSiteCreator(): boolean {
1118 this.props.admins &&
1119 UserService.Instance.user &&
1120 this.props.post.creator_id != UserService.Instance.user.id &&
1121 UserService.Instance.user.id == this.props.admins[0].id
1125 handlePostLike(i: PostListing) {
1126 if (!UserService.Instance.user) {
1127 this.context.router.history.push(`/login`);
1130 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1132 if (i.state.my_vote == 1) {
1135 } else if (i.state.my_vote == -1) {
1136 i.state.downvotes--;
1144 i.state.my_vote = new_vote;
1146 let form: CreatePostLikeForm = {
1147 post_id: i.props.post.id,
1148 score: i.state.my_vote,
1151 WebSocketService.Instance.likePost(form);
1152 i.setState(i.state);
1156 handlePostDisLike(i: PostListing) {
1157 if (!UserService.Instance.user) {
1158 this.context.router.history.push(`/login`);
1161 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1163 if (i.state.my_vote == 1) {
1166 i.state.downvotes++;
1167 } else if (i.state.my_vote == -1) {
1168 i.state.downvotes--;
1171 i.state.downvotes++;
1175 i.state.my_vote = new_vote;
1177 let form: CreatePostLikeForm = {
1178 post_id: i.props.post.id,
1179 score: i.state.my_vote,
1182 WebSocketService.Instance.likePost(form);
1183 i.setState(i.state);
1187 handleEditClick(i: PostListing) {
1188 i.state.showEdit = true;
1189 i.setState(i.state);
1192 handleEditCancel() {
1193 this.state.showEdit = false;
1194 this.setState(this.state);
1197 // The actual editing is done in the recieve for post
1199 this.state.showEdit = false;
1200 this.setState(this.state);
1203 handleDeleteClick(i: PostListing) {
1204 let deleteForm: DeletePostForm = {
1205 edit_id: i.props.post.id,
1206 deleted: !i.props.post.deleted,
1209 WebSocketService.Instance.deletePost(deleteForm);
1212 handleSavePostClick(i: PostListing) {
1213 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1214 let form: SavePostForm = {
1215 post_id: i.props.post.id,
1219 WebSocketService.Instance.savePost(form);
1222 get crossPostParams(): string {
1223 let params = `?title=${this.props.post.name}`;
1224 let post = this.props.post;
1227 params += `&url=${post.url}`;
1229 if (this.props.post.body) {
1230 params += `&body=${this.props.post.body}`;
1235 handleModRemoveShow(i: PostListing) {
1236 i.state.showRemoveDialog = true;
1237 i.setState(i.state);
1240 handleModRemoveReasonChange(i: PostListing, event: any) {
1241 i.state.removeReason = event.target.value;
1242 i.setState(i.state);
1245 handleModRemoveSubmit(i: PostListing) {
1246 event.preventDefault();
1247 let form: RemovePostForm = {
1248 edit_id: i.props.post.id,
1249 removed: !i.props.post.removed,
1250 reason: i.state.removeReason,
1253 WebSocketService.Instance.removePost(form);
1255 i.state.showRemoveDialog = false;
1256 i.setState(i.state);
1259 handleModLock(i: PostListing) {
1260 let form: LockPostForm = {
1261 edit_id: i.props.post.id,
1262 locked: !i.props.post.locked,
1265 WebSocketService.Instance.lockPost(form);
1268 handleModSticky(i: PostListing) {
1269 let form: StickyPostForm = {
1270 edit_id: i.props.post.id,
1271 stickied: !i.props.post.stickied,
1274 WebSocketService.Instance.stickyPost(form);
1277 handleModBanFromCommunityShow(i: PostListing) {
1278 i.state.showBanDialog = true;
1279 i.state.banType = BanType.Community;
1280 i.setState(i.state);
1283 handleModBanShow(i: PostListing) {
1284 i.state.showBanDialog = true;
1285 i.state.banType = BanType.Site;
1286 i.setState(i.state);
1289 handleModBanReasonChange(i: PostListing, event: any) {
1290 i.state.banReason = event.target.value;
1291 i.setState(i.state);
1294 handleModBanExpiresChange(i: PostListing, event: any) {
1295 i.state.banExpires = event.target.value;
1296 i.setState(i.state);
1299 handleModBanFromCommunitySubmit(i: PostListing) {
1300 i.state.banType = BanType.Community;
1301 i.setState(i.state);
1302 i.handleModBanBothSubmit(i);
1305 handleModBanSubmit(i: PostListing) {
1306 i.state.banType = BanType.Site;
1307 i.setState(i.state);
1308 i.handleModBanBothSubmit(i);
1311 handleModBanBothSubmit(i: PostListing) {
1312 event.preventDefault();
1314 if (i.state.banType == BanType.Community) {
1315 let form: BanFromCommunityForm = {
1316 user_id: i.props.post.creator_id,
1317 community_id: i.props.post.community_id,
1318 ban: !i.props.post.banned_from_community,
1319 reason: i.state.banReason,
1320 expires: getUnixTime(i.state.banExpires),
1322 WebSocketService.Instance.banFromCommunity(form);
1324 let form: BanUserForm = {
1325 user_id: i.props.post.creator_id,
1326 ban: !i.props.post.banned,
1327 reason: i.state.banReason,
1328 expires: getUnixTime(i.state.banExpires),
1330 WebSocketService.Instance.banUser(form);
1333 i.state.showBanDialog = false;
1334 i.setState(i.state);
1337 handleAddModToCommunity(i: PostListing) {
1338 let form: AddModToCommunityForm = {
1339 user_id: i.props.post.creator_id,
1340 community_id: i.props.post.community_id,
1343 WebSocketService.Instance.addModToCommunity(form);
1344 i.setState(i.state);
1347 handleAddAdmin(i: PostListing) {
1348 let form: AddAdminForm = {
1349 user_id: i.props.post.creator_id,
1352 WebSocketService.Instance.addAdmin(form);
1353 i.setState(i.state);
1356 handleShowConfirmTransferCommunity(i: PostListing) {
1357 i.state.showConfirmTransferCommunity = true;
1358 i.setState(i.state);
1361 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1362 i.state.showConfirmTransferCommunity = false;
1363 i.setState(i.state);
1366 handleTransferCommunity(i: PostListing) {
1367 let form: TransferCommunityForm = {
1368 community_id: i.props.post.community_id,
1369 user_id: i.props.post.creator_id,
1371 WebSocketService.Instance.transferCommunity(form);
1372 i.state.showConfirmTransferCommunity = false;
1373 i.setState(i.state);
1376 handleShowConfirmTransferSite(i: PostListing) {
1377 i.state.showConfirmTransferSite = true;
1378 i.setState(i.state);
1381 handleCancelShowConfirmTransferSite(i: PostListing) {
1382 i.state.showConfirmTransferSite = false;
1383 i.setState(i.state);
1386 handleTransferSite(i: PostListing) {
1387 let form: TransferSiteForm = {
1388 user_id: i.props.post.creator_id,
1390 WebSocketService.Instance.transferSite(form);
1391 i.state.showConfirmTransferSite = false;
1392 i.setState(i.state);
1395 handleImageExpandClick(i: PostListing) {
1396 i.state.imageExpanded = !i.state.imageExpanded;
1397 i.setState(i.state);
1400 handleViewSource(i: PostListing) {
1401 i.state.viewSource = !i.state.viewSource;
1402 i.setState(i.state);
1405 handleShowAdvanced(i: PostListing) {
1406 i.state.showAdvanced = !i.state.showAdvanced;
1407 i.setState(i.state);
1411 get pointsTippy(): string {
1412 let points = i18n.t('number_of_points', {
1413 count: this.state.score,
1416 let upvotes = i18n.t('number_of_upvotes', {
1417 count: this.state.upvotes,
1420 let downvotes = i18n.t('number_of_downvotes', {
1421 count: this.state.downvotes,
1424 return `${points} • ${upvotes} • ${downvotes}`;