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) && (
519 class="unselectable pointer ml-3"
520 data-tippy-content={this.pointsTippy}
522 <li className="list-inline-item">
524 className={`btn-animate btn btn-link p-0 ${
525 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
527 onClick={linkEvent(this, this.handlePostLike)}
529 <svg class="small icon icon-inline mx-1">
530 <use xlinkHref="#icon-arrow-up1"></use>
535 <li className="list-inline-item">
537 className={`btn-animate btn btn-link p-0 ${
538 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
540 onClick={linkEvent(this, this.handlePostDisLike)}
542 <svg class="small icon icon-inline mx-1">
543 <use xlinkHref="#icon-arrow-down1"></use>
545 {this.state.downvotes}
557 this.props.post.duplicates && (
558 <ul class="list-inline mb-1 small text-muted">
560 <li className="list-inline-item mr-2">
561 {i18n.t('cross_posted_to')}
563 {this.props.post.duplicates.map(post => (
564 <li className="list-inline-item mr-2">
565 <Link to={`/post/${post.id}`}>{post.community_name}</Link>
575 let post = this.props.post;
577 <ul class="list-inline mb-1 text-muted font-weight-bold">
578 {UserService.Instance.user && (
580 {this.props.showBody && (
582 <li className="list-inline-item">
584 class="btn btn-link btn-animate text-muted"
585 onClick={linkEvent(this, this.handleSavePostClick)}
587 post.saved ? i18n.t('unsave') : i18n.t('save')
591 class={`icon icon-inline ${post.saved && 'text-warning'}`}
593 <use xlinkHref="#icon-star"></use>
597 <li className="list-inline-item">
599 class="btn btn-link btn-animate text-muted"
600 to={`/create_post${this.crossPostParams}`}
601 title={i18n.t('cross_post')}
603 <svg class="icon icon-inline">
604 <use xlinkHref="#icon-copy"></use>
610 {this.myPost && this.props.showBody && (
612 <li className="list-inline-item">
614 class="btn btn-link btn-animate text-muted"
615 onClick={linkEvent(this, this.handleEditClick)}
616 data-tippy-content={i18n.t('edit')}
618 <svg class="icon icon-inline">
619 <use xlinkHref="#icon-edit"></use>
623 <li className="list-inline-item">
625 class="btn btn-link btn-animate text-muted"
626 onClick={linkEvent(this, this.handleDeleteClick)}
628 !post.deleted ? i18n.t('delete') : i18n.t('restore')
632 class={`icon icon-inline ${
633 post.deleted && 'text-danger'
636 <use xlinkHref="#icon-trash"></use>
643 {!this.state.showAdvanced && this.props.showBody ? (
644 <li className="list-inline-item">
646 class="btn btn-link btn-animate text-muted"
647 onClick={linkEvent(this, this.handleShowAdvanced)}
648 data-tippy-content={i18n.t('more')}
650 <svg class="icon icon-inline">
651 <use xlinkHref="#icon-more-vertical"></use>
657 {this.props.showBody && post.body && (
658 <li className="list-inline-item">
660 class="btn btn-link btn-animate text-muted"
661 onClick={linkEvent(this, this.handleViewSource)}
662 data-tippy-content={i18n.t('view_source')}
665 class={`icon icon-inline ${
666 this.state.viewSource && 'text-success'
669 <use xlinkHref="#icon-file-text"></use>
674 {this.canModOnSelf && (
676 <li className="list-inline-item">
678 class="btn btn-link btn-animate text-muted"
679 onClick={linkEvent(this, this.handleModLock)}
681 post.locked ? i18n.t('unlock') : i18n.t('lock')
685 class={`icon icon-inline ${
686 post.locked && 'text-danger'
689 <use xlinkHref="#icon-lock"></use>
693 <li className="list-inline-item">
695 class="btn btn-link btn-animate text-muted"
696 onClick={linkEvent(this, this.handleModSticky)}
698 post.stickied ? i18n.t('unsticky') : i18n.t('sticky')
702 class={`icon icon-inline ${
703 post.stickied && 'text-success'
706 <use xlinkHref="#icon-pin"></use>
712 {/* Mods can ban from community, and appoint as mods to community */}
713 {(this.canMod || this.canAdmin) && (
714 <li className="list-inline-item">
718 onClick={linkEvent(this, this.handleModRemoveShow)}
725 onClick={linkEvent(this, this.handleModRemoveSubmit)}
735 <li className="list-inline-item">
736 {!post.banned_from_community ? (
741 this.handleModBanFromCommunityShow
751 this.handleModBanFromCommunitySubmit
759 {!post.banned_from_community && post.creator_local && (
760 <li className="list-inline-item">
765 this.handleAddModToCommunity
769 ? i18n.t('remove_as_mod')
770 : i18n.t('appoint_as_mod')}
776 {/* Community creators and admins can transfer community to another mod */}
777 {(this.amCommunityCreator || this.canAdmin) &&
779 post.creator_local && (
780 <li className="list-inline-item">
781 {!this.state.showConfirmTransferCommunity ? (
786 this.handleShowConfirmTransferCommunity
789 {i18n.t('transfer_community')}
793 <span class="d-inline-block mr-1">
794 {i18n.t('are_you_sure')}
797 class="pointer d-inline-block mr-1"
800 this.handleTransferCommunity
806 class="pointer d-inline-block"
809 this.handleCancelShowConfirmTransferCommunity
818 {/* Admins can ban from all, and appoint other admins */}
822 <li className="list-inline-item">
826 onClick={linkEvent(this, this.handleModBanShow)}
828 {i18n.t('ban_from_site')}
833 onClick={linkEvent(this, this.handleModBanSubmit)}
835 {i18n.t('unban_from_site')}
840 {!post.banned && post.creator_local && (
841 <li className="list-inline-item">
844 onClick={linkEvent(this, this.handleAddAdmin)}
847 ? i18n.t('remove_as_admin')
848 : i18n.t('appoint_as_admin')}
854 {/* Site Creator can transfer to another admin */}
855 {this.amSiteCreator && this.isAdmin && (
856 <li className="list-inline-item">
857 {!this.state.showConfirmTransferSite ? (
862 this.handleShowConfirmTransferSite
865 {i18n.t('transfer_site')}
869 <span class="d-inline-block mr-1">
870 {i18n.t('are_you_sure')}
873 class="pointer d-inline-block mr-1"
874 onClick={linkEvent(this, this.handleTransferSite)}
879 class="pointer d-inline-block"
882 this.handleCancelShowConfirmTransferSite
899 removeAndBanDialogs() {
900 let post = this.props.post;
903 {this.state.showRemoveDialog && (
906 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
910 class="form-control mr-2"
911 placeholder={i18n.t('reason')}
912 value={this.state.removeReason}
913 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
915 <button type="submit" class="btn btn-secondary">
916 {i18n.t('remove_post')}
920 {this.state.showBanDialog && (
921 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
922 <div class="form-group row">
923 <label class="col-form-label" htmlFor="post-listing-reason">
928 id="post-listing-reason"
929 class="form-control mr-2"
930 placeholder={i18n.t('reason')}
931 value={this.state.banReason}
932 onInput={linkEvent(this, this.handleModBanReasonChange)}
935 {/* TODO hold off on expires until later */}
936 {/* <div class="form-group row"> */}
937 {/* <label class="col-form-label">Expires</label> */}
938 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
940 <div class="form-group row">
941 <button type="submit" class="btn btn-secondary">
942 {i18n.t('ban')} {post.creator_name}
952 return this.props.post.thumbnail_url || isImage(this.props.post.url) ? (
954 <div className={`${this.state.imageExpanded ? 'col-12' : 'col-8'}`}>
955 {this.postTitleLine()}
958 {/* Post body prev or thumbnail */}
959 {!this.state.imageExpanded && this.thumbnail()}
967 showMobilePreview() {
969 this.props.post.body &&
970 !this.props.showBody && (
972 className="md-div mb-1"
973 dangerouslySetInnerHTML={{
974 __html: md.render(previewLines(this.props.post.body)),
984 {/* The mobile view*/}
985 <div class="d-block d-sm-none">
990 {/* If it has a thumbnail, do a right aligned thumbnail */}
991 {this.mobileThumbnail()}
993 {/* Show a preview of the post body */}
994 {this.showMobilePreview()}
996 {this.commentsLine(true)}
997 {this.duplicatesLine()}
999 {this.removeAndBanDialogs()}
1004 {/* The larger view*/}
1005 <div class="d-none d-sm-block">
1008 {!this.state.imageExpanded && (
1009 <div class="col-sm-2 pr-0">
1010 <div class="">{this.thumbnail()}</div>
1015 this.state.imageExpanded ? 'col-12' : 'col-12 col-sm-9'
1019 <div className="col-12">
1020 {this.postTitleLine()}
1021 {this.createdLine()}
1022 {this.commentsLine()}
1023 {this.duplicatesLine()}
1024 {this.postActions()}
1025 {this.removeAndBanDialogs()}
1035 private get myPost(): boolean {
1037 UserService.Instance.user &&
1038 this.props.post.creator_id == UserService.Instance.user.id
1042 get isMod(): boolean {
1044 this.props.moderators &&
1046 this.props.moderators.map(m => m.user_id),
1047 this.props.post.creator_id
1052 get isAdmin(): boolean {
1054 this.props.admins &&
1056 this.props.admins.map(a => a.id),
1057 this.props.post.creator_id
1062 get canMod(): boolean {
1063 if (this.props.admins && this.props.moderators) {
1064 let adminsThenMods = this.props.admins
1066 .concat(this.props.moderators.map(m => m.user_id));
1069 UserService.Instance.user,
1071 this.props.post.creator_id
1078 get canModOnSelf(): boolean {
1079 if (this.props.admins && this.props.moderators) {
1080 let adminsThenMods = this.props.admins
1082 .concat(this.props.moderators.map(m => m.user_id));
1085 UserService.Instance.user,
1087 this.props.post.creator_id,
1095 get canAdmin(): boolean {
1097 this.props.admins &&
1099 UserService.Instance.user,
1100 this.props.admins.map(a => a.id),
1101 this.props.post.creator_id
1106 get amCommunityCreator(): boolean {
1108 this.props.moderators &&
1109 UserService.Instance.user &&
1110 this.props.post.creator_id != UserService.Instance.user.id &&
1111 UserService.Instance.user.id == this.props.moderators[0].user_id
1115 get amSiteCreator(): boolean {
1117 this.props.admins &&
1118 UserService.Instance.user &&
1119 this.props.post.creator_id != UserService.Instance.user.id &&
1120 UserService.Instance.user.id == this.props.admins[0].id
1124 handlePostLike(i: PostListing) {
1125 if (!UserService.Instance.user) {
1126 this.context.router.history.push(`/login`);
1129 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1131 if (i.state.my_vote == 1) {
1134 } else if (i.state.my_vote == -1) {
1135 i.state.downvotes--;
1143 i.state.my_vote = new_vote;
1145 let form: CreatePostLikeForm = {
1146 post_id: i.props.post.id,
1147 score: i.state.my_vote,
1150 WebSocketService.Instance.likePost(form);
1151 i.setState(i.state);
1155 handlePostDisLike(i: PostListing) {
1156 if (!UserService.Instance.user) {
1157 this.context.router.history.push(`/login`);
1160 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1162 if (i.state.my_vote == 1) {
1165 i.state.downvotes++;
1166 } else if (i.state.my_vote == -1) {
1167 i.state.downvotes--;
1170 i.state.downvotes++;
1174 i.state.my_vote = new_vote;
1176 let form: CreatePostLikeForm = {
1177 post_id: i.props.post.id,
1178 score: i.state.my_vote,
1181 WebSocketService.Instance.likePost(form);
1182 i.setState(i.state);
1186 handleEditClick(i: PostListing) {
1187 i.state.showEdit = true;
1188 i.setState(i.state);
1191 handleEditCancel() {
1192 this.state.showEdit = false;
1193 this.setState(this.state);
1196 // The actual editing is done in the recieve for post
1198 this.state.showEdit = false;
1199 this.setState(this.state);
1202 handleDeleteClick(i: PostListing) {
1203 let deleteForm: DeletePostForm = {
1204 edit_id: i.props.post.id,
1205 deleted: !i.props.post.deleted,
1208 WebSocketService.Instance.deletePost(deleteForm);
1211 handleSavePostClick(i: PostListing) {
1212 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1213 let form: SavePostForm = {
1214 post_id: i.props.post.id,
1218 WebSocketService.Instance.savePost(form);
1221 get crossPostParams(): string {
1222 let params = `?title=${this.props.post.name}`;
1223 let post = this.props.post;
1226 params += `&url=${post.url}`;
1228 if (this.props.post.body) {
1229 params += `&body=${this.props.post.body}`;
1234 handleModRemoveShow(i: PostListing) {
1235 i.state.showRemoveDialog = true;
1236 i.setState(i.state);
1239 handleModRemoveReasonChange(i: PostListing, event: any) {
1240 i.state.removeReason = event.target.value;
1241 i.setState(i.state);
1244 handleModRemoveSubmit(i: PostListing) {
1245 event.preventDefault();
1246 let form: RemovePostForm = {
1247 edit_id: i.props.post.id,
1248 removed: !i.props.post.removed,
1249 reason: i.state.removeReason,
1252 WebSocketService.Instance.removePost(form);
1254 i.state.showRemoveDialog = false;
1255 i.setState(i.state);
1258 handleModLock(i: PostListing) {
1259 let form: LockPostForm = {
1260 edit_id: i.props.post.id,
1261 locked: !i.props.post.locked,
1264 WebSocketService.Instance.lockPost(form);
1267 handleModSticky(i: PostListing) {
1268 let form: StickyPostForm = {
1269 edit_id: i.props.post.id,
1270 stickied: !i.props.post.stickied,
1273 WebSocketService.Instance.stickyPost(form);
1276 handleModBanFromCommunityShow(i: PostListing) {
1277 i.state.showBanDialog = true;
1278 i.state.banType = BanType.Community;
1279 i.setState(i.state);
1282 handleModBanShow(i: PostListing) {
1283 i.state.showBanDialog = true;
1284 i.state.banType = BanType.Site;
1285 i.setState(i.state);
1288 handleModBanReasonChange(i: PostListing, event: any) {
1289 i.state.banReason = event.target.value;
1290 i.setState(i.state);
1293 handleModBanExpiresChange(i: PostListing, event: any) {
1294 i.state.banExpires = event.target.value;
1295 i.setState(i.state);
1298 handleModBanFromCommunitySubmit(i: PostListing) {
1299 i.state.banType = BanType.Community;
1300 i.setState(i.state);
1301 i.handleModBanBothSubmit(i);
1304 handleModBanSubmit(i: PostListing) {
1305 i.state.banType = BanType.Site;
1306 i.setState(i.state);
1307 i.handleModBanBothSubmit(i);
1310 handleModBanBothSubmit(i: PostListing) {
1311 event.preventDefault();
1313 if (i.state.banType == BanType.Community) {
1314 let form: BanFromCommunityForm = {
1315 user_id: i.props.post.creator_id,
1316 community_id: i.props.post.community_id,
1317 ban: !i.props.post.banned_from_community,
1318 reason: i.state.banReason,
1319 expires: getUnixTime(i.state.banExpires),
1321 WebSocketService.Instance.banFromCommunity(form);
1323 let form: BanUserForm = {
1324 user_id: i.props.post.creator_id,
1325 ban: !i.props.post.banned,
1326 reason: i.state.banReason,
1327 expires: getUnixTime(i.state.banExpires),
1329 WebSocketService.Instance.banUser(form);
1332 i.state.showBanDialog = false;
1333 i.setState(i.state);
1336 handleAddModToCommunity(i: PostListing) {
1337 let form: AddModToCommunityForm = {
1338 user_id: i.props.post.creator_id,
1339 community_id: i.props.post.community_id,
1342 WebSocketService.Instance.addModToCommunity(form);
1343 i.setState(i.state);
1346 handleAddAdmin(i: PostListing) {
1347 let form: AddAdminForm = {
1348 user_id: i.props.post.creator_id,
1351 WebSocketService.Instance.addAdmin(form);
1352 i.setState(i.state);
1355 handleShowConfirmTransferCommunity(i: PostListing) {
1356 i.state.showConfirmTransferCommunity = true;
1357 i.setState(i.state);
1360 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1361 i.state.showConfirmTransferCommunity = false;
1362 i.setState(i.state);
1365 handleTransferCommunity(i: PostListing) {
1366 let form: TransferCommunityForm = {
1367 community_id: i.props.post.community_id,
1368 user_id: i.props.post.creator_id,
1370 WebSocketService.Instance.transferCommunity(form);
1371 i.state.showConfirmTransferCommunity = false;
1372 i.setState(i.state);
1375 handleShowConfirmTransferSite(i: PostListing) {
1376 i.state.showConfirmTransferSite = true;
1377 i.setState(i.state);
1380 handleCancelShowConfirmTransferSite(i: PostListing) {
1381 i.state.showConfirmTransferSite = false;
1382 i.setState(i.state);
1385 handleTransferSite(i: PostListing) {
1386 let form: TransferSiteForm = {
1387 user_id: i.props.post.creator_id,
1389 WebSocketService.Instance.transferSite(form);
1390 i.state.showConfirmTransferSite = false;
1391 i.setState(i.state);
1394 handleImageExpandClick(i: PostListing) {
1395 i.state.imageExpanded = !i.state.imageExpanded;
1396 i.setState(i.state);
1399 handleViewSource(i: PostListing) {
1400 i.state.viewSource = !i.state.viewSource;
1401 i.setState(i.state);
1404 handleShowAdvanced(i: PostListing) {
1405 i.state.showAdvanced = !i.state.showAdvanced;
1406 i.setState(i.state);
1410 get pointsTippy(): string {
1411 let points = i18n.t('number_of_points', {
1412 count: this.state.score,
1415 let upvotes = i18n.t('number_of_upvotes', {
1416 count: this.state.upvotes,
1419 let downvotes = i18n.t('number_of_downvotes', {
1420 count: this.state.downvotes,
1423 return `${points} • ${upvotes} • ${downvotes}`;