1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { WebSocketService, UserService } from '../services';
16 AddModToCommunityForm,
19 TransferCommunityForm,
20 } from 'lemmy-js-client';
21 import { BanType } 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;
51 showConfirmTransferSite: boolean;
52 showConfirmTransferCommunity: boolean;
53 imageExpanded: boolean;
55 showAdvanced: boolean;
62 interface PostListingProps {
64 showCommunity?: boolean;
66 moderators?: Array<CommunityUser>;
67 admins?: Array<UserView>;
68 enableDownvotes: boolean;
72 export class PostListing extends Component<PostListingProps, PostListingState> {
73 private emptyState: PostListingState = {
75 showRemoveDialog: false,
81 banType: BanType.Community,
82 showConfirmTransferSite: false,
83 showConfirmTransferCommunity: false,
87 my_vote: this.props.post.my_vote,
88 score: this.props.post.score,
89 upvotes: this.props.post.upvotes,
90 downvotes: this.props.post.downvotes,
93 constructor(props: any, context: any) {
94 super(props, context);
96 this.state = this.emptyState;
97 this.handlePostLike = this.handlePostLike.bind(this);
98 this.handlePostDisLike = this.handlePostDisLike.bind(this);
99 this.handleEditPost = this.handleEditPost.bind(this);
100 this.handleEditCancel = this.handleEditCancel.bind(this);
103 componentWillReceiveProps(nextProps: PostListingProps) {
104 this.state.my_vote = nextProps.post.my_vote;
105 this.state.upvotes = nextProps.post.upvotes;
106 this.state.downvotes = nextProps.post.downvotes;
107 this.state.score = nextProps.post.score;
108 if (this.props.post.id !== nextProps.post.id) {
109 this.state.imageExpanded = false;
111 this.setState(this.state);
117 {!this.state.showEdit ? (
125 post={this.props.post}
126 onEdit={this.handleEditPost}
127 onCancel={this.handleEditCancel}
128 enableNsfw={this.props.enableNsfw}
129 enableDownvotes={this.props.enableDownvotes}
141 {this.props.post.url &&
142 this.props.showBody &&
143 this.props.post.embed_title && (
144 <IFramelyCard post={this.props.post} />
146 {this.props.showBody && this.props.post.body && (
148 {this.state.viewSource ? (
149 <pre>{this.props.post.body}</pre>
153 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
163 imgThumb(src: string) {
164 let post = this.props.post;
167 className={`img-fluid thumbnail rounded ${
168 post.nsfw || post.community_nsfw ? 'img-blur' : ''
175 getImage(thumbnail: boolean = false) {
176 let post = this.props.post;
177 if (isImage(post.url)) {
178 if (post.url.includes('pictrs')) {
179 return pictrsImage(post.url, thumbnail);
180 } else if (post.thumbnail_url) {
181 return pictrsImage(post.thumbnail_url, thumbnail);
185 } else if (post.thumbnail_url) {
186 return pictrsImage(post.thumbnail_url, thumbnail);
191 let post = this.props.post;
193 if (isImage(post.url)) {
196 class="float-right text-body pointer d-inline-block position-relative"
197 data-tippy-content={i18n.t('expand_here')}
198 onClick={linkEvent(this, this.handleImageExpandClick)}
200 {this.imgThumb(this.getImage(true))}
201 <svg class="icon mini-overlay">
202 <use xlinkHref="#icon-image"></use>
206 } else if (post.thumbnail_url) {
209 class="float-right text-body d-inline-block position-relative"
215 {this.imgThumb(this.getImage(true))}
216 <svg class="icon mini-overlay">
217 <use xlinkHref="#icon-external-link"></use>
221 } else if (post.url) {
222 if (isVideo(post.url)) {
224 <div class="embed-responsive embed-responsive-16by9">
230 class="embed-responsive-item"
232 <source src={post.url} type="video/mp4" />
239 className="text-body"
245 <div class="thumbnail rounded bg-light d-flex justify-content-center">
246 <svg class="icon d-flex align-items-center">
247 <use xlinkHref="#icon-external-link"></use>
256 className="text-body"
257 to={`/post/${post.id}`}
258 title={i18n.t('comments')}
260 <div class="thumbnail rounded bg-light d-flex justify-content-center">
261 <svg class="icon d-flex align-items-center">
262 <use xlinkHref="#icon-message-square"></use>
271 let post = this.props.post;
273 <ul class="list-inline mb-1 text-muted small">
274 <li className="list-inline-item">
277 name: post.creator_name,
278 preferred_username: post.creator_preferred_username,
279 avatar: post.creator_avatar,
281 local: post.creator_local,
282 actor_id: post.creator_actor_id,
283 published: post.creator_published,
288 <span className="mx-1 badge badge-light">{i18n.t('mod')}</span>
291 <span className="mx-1 badge badge-light">{i18n.t('admin')}</span>
293 {(post.banned_from_community || post.banned) && (
294 <span className="mx-1 badge badge-danger">{i18n.t('banned')}</span>
296 {this.props.showCommunity && (
298 <span class="mx-1"> {i18n.t('to')} </span>
301 name: post.community_name,
302 id: post.community_id,
303 local: post.community_local,
304 actor_id: post.community_actor_id,
305 icon: post.community_icon,
311 <li className="list-inline-item">•</li>
312 {post.url && !(hostname(post.url) == window.location.hostname) && (
314 <li className="list-inline-item">
316 className="text-muted font-italic"
325 <li className="list-inline-item">•</li>
328 <li className="list-inline-item">
330 <MomentTime data={post} />
335 <li className="list-inline-item">•</li>
336 <li className="list-inline-item">
337 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
339 className="text-muted"
340 data-tippy-content={md.render(previewLines(post.body))}
341 data-tippy-allowHtml={true}
342 to={`/post/${post.id}`}
344 <svg class="mr-1 icon icon-inline">
345 <use xlinkHref="#icon-book-open"></use>
357 <div className={`vote-bar col-1 pr-0 small text-center`}>
359 className={`btn-animate btn btn-link p-0 ${
360 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
362 onClick={linkEvent(this, this.handlePostLike)}
363 data-tippy-content={i18n.t('upvote')}
365 <svg class="icon upvote">
366 <use xlinkHref="#icon-arrow-up1"></use>
370 class={`unselectable pointer font-weight-bold text-muted px-1`}
371 data-tippy-content={this.pointsTippy}
375 {this.props.enableDownvotes && (
377 className={`btn-animate btn btn-link p-0 ${
378 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
380 onClick={linkEvent(this, this.handlePostDisLike)}
381 data-tippy-content={i18n.t('downvote')}
383 <svg class="icon downvote">
384 <use xlinkHref="#icon-arrow-down1"></use>
393 let post = this.props.post;
395 <div className="post-title overflow-hidden">
397 {this.props.showBody && post.url ? (
399 className={!post.stickied ? 'text-body' : 'text-primary'}
409 className={!post.stickied ? 'text-body' : 'text-primary'}
410 to={`/post/${post.id}`}
411 title={i18n.t('comments')}
416 {(isImage(post.url) || this.props.post.thumbnail_url) && (
418 {!this.state.imageExpanded ? (
420 class="text-monospace unselectable pointer ml-2 text-muted small"
421 data-tippy-content={i18n.t('expand_here')}
422 onClick={linkEvent(this, this.handleImageExpandClick)}
424 <svg class="icon icon-inline">
425 <use xlinkHref="#icon-plus-square"></use>
431 class="text-monospace unselectable pointer ml-2 text-muted small"
432 onClick={linkEvent(this, this.handleImageExpandClick)}
434 <svg class="icon icon-inline">
435 <use xlinkHref="#icon-minus-square"></use>
441 onClick={linkEvent(this, this.handleImageExpandClick)}
444 class="img-fluid img-expanded"
445 src={this.getImage()}
454 <small className="ml-2 text-muted font-italic">
460 className="unselectable pointer ml-2 text-muted font-italic"
461 data-tippy-content={i18n.t('deleted')}
463 <svg class={`icon icon-inline text-danger`}>
464 <use xlinkHref="#icon-trash"></use>
470 className="unselectable pointer ml-2 text-muted font-italic"
471 data-tippy-content={i18n.t('locked')}
473 <svg class={`icon icon-inline text-danger`}>
474 <use xlinkHref="#icon-lock"></use>
480 className="unselectable pointer ml-2 text-muted font-italic"
481 data-tippy-content={i18n.t('stickied')}
483 <svg class={`icon icon-inline text-primary`}>
484 <use xlinkHref="#icon-pin"></use>
489 <small className="ml-2 text-muted font-italic">
498 commentsLine(showVotes: boolean = false) {
499 let post = this.props.post;
501 <ul class="d-flex align-items-center list-inline mb-1 text-muted small">
502 <li className="list-inline-item">
504 className="text-muted"
505 title={i18n.t('number_of_comments', {
506 count: post.number_of_comments,
508 to={`/post/${post.id}`}
510 <svg class="mr-1 icon icon-inline">
511 <use xlinkHref="#icon-message-square"></use>
513 {i18n.t('number_of_comments', {
514 count: post.number_of_comments,
518 {(showVotes || this.state.upvotes !== this.state.score) && (
521 class="unselectable pointer ml-3"
522 data-tippy-content={this.pointsTippy}
524 <li className="list-inline-item">
526 className={`btn-animate btn btn-link p-0 ${
527 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
529 onClick={linkEvent(this, this.handlePostLike)}
531 <svg class="small icon icon-inline mx-1">
532 <use xlinkHref="#icon-arrow-up1"></use>
537 <li className="list-inline-item">
539 className={`btn-animate btn btn-link p-0 ${
540 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
542 onClick={linkEvent(this, this.handlePostDisLike)}
544 <svg class="small icon icon-inline mx-1">
545 <use xlinkHref="#icon-arrow-down1"></use>
547 {this.state.downvotes}
559 this.props.post.duplicates && (
560 <ul class="list-inline mb-1 small text-muted">
562 <li className="list-inline-item mr-2">
563 {i18n.t('cross_posted_to')}
565 {this.props.post.duplicates.map(post => (
566 <li className="list-inline-item mr-2">
567 <Link to={`/post/${post.id}`}>{post.community_name}</Link>
577 let post = this.props.post;
579 <ul class="list-inline mb-1 text-muted font-weight-bold">
580 {UserService.Instance.user && (
582 {this.props.showBody && (
584 <li className="list-inline-item">
586 class="btn btn-link btn-animate text-muted"
587 onClick={linkEvent(this, this.handleSavePostClick)}
589 post.saved ? i18n.t('unsave') : i18n.t('save')
593 class={`icon icon-inline ${post.saved && 'text-warning'}`}
595 <use xlinkHref="#icon-star"></use>
599 <li className="list-inline-item">
601 class="btn btn-link btn-animate text-muted"
602 to={`/create_post${this.crossPostParams}`}
603 title={i18n.t('cross_post')}
605 <svg class="icon icon-inline">
606 <use xlinkHref="#icon-copy"></use>
612 {this.myPost && this.props.showBody && (
614 <li className="list-inline-item">
616 class="btn btn-link btn-animate text-muted"
617 onClick={linkEvent(this, this.handleEditClick)}
618 data-tippy-content={i18n.t('edit')}
620 <svg class="icon icon-inline">
621 <use xlinkHref="#icon-edit"></use>
625 <li className="list-inline-item">
627 class="btn btn-link btn-animate text-muted"
628 onClick={linkEvent(this, this.handleDeleteClick)}
630 !post.deleted ? i18n.t('delete') : i18n.t('restore')
634 class={`icon icon-inline ${
635 post.deleted && 'text-danger'
638 <use xlinkHref="#icon-trash"></use>
645 {!this.state.showAdvanced && this.props.showBody ? (
646 <li className="list-inline-item">
648 class="btn btn-link btn-animate text-muted"
649 onClick={linkEvent(this, this.handleShowAdvanced)}
650 data-tippy-content={i18n.t('more')}
652 <svg class="icon icon-inline">
653 <use xlinkHref="#icon-more-vertical"></use>
659 {this.props.showBody && post.body && (
660 <li className="list-inline-item">
662 class="btn btn-link btn-animate text-muted"
663 onClick={linkEvent(this, this.handleViewSource)}
664 data-tippy-content={i18n.t('view_source')}
667 class={`icon icon-inline ${
668 this.state.viewSource && 'text-success'
671 <use xlinkHref="#icon-file-text"></use>
676 {this.canModOnSelf && (
678 <li className="list-inline-item">
680 class="btn btn-link btn-animate text-muted"
681 onClick={linkEvent(this, this.handleModLock)}
683 post.locked ? i18n.t('unlock') : i18n.t('lock')
687 class={`icon icon-inline ${
688 post.locked && 'text-danger'
691 <use xlinkHref="#icon-lock"></use>
695 <li className="list-inline-item">
697 class="btn btn-link btn-animate text-muted"
698 onClick={linkEvent(this, this.handleModSticky)}
700 post.stickied ? i18n.t('unsticky') : i18n.t('sticky')
704 class={`icon icon-inline ${
705 post.stickied && 'text-success'
708 <use xlinkHref="#icon-pin"></use>
714 {/* Mods can ban from community, and appoint as mods to community */}
715 {(this.canMod || this.canAdmin) && (
716 <li className="list-inline-item">
720 onClick={linkEvent(this, this.handleModRemoveShow)}
727 onClick={linkEvent(this, this.handleModRemoveSubmit)}
737 <li className="list-inline-item">
738 {!post.banned_from_community ? (
743 this.handleModBanFromCommunityShow
753 this.handleModBanFromCommunitySubmit
761 {!post.banned_from_community && post.creator_local && (
762 <li className="list-inline-item">
767 this.handleAddModToCommunity
771 ? i18n.t('remove_as_mod')
772 : i18n.t('appoint_as_mod')}
778 {/* Community creators and admins can transfer community to another mod */}
779 {(this.amCommunityCreator || this.canAdmin) &&
781 post.creator_local && (
782 <li className="list-inline-item">
783 {!this.state.showConfirmTransferCommunity ? (
788 this.handleShowConfirmTransferCommunity
791 {i18n.t('transfer_community')}
795 <span class="d-inline-block mr-1">
796 {i18n.t('are_you_sure')}
799 class="pointer d-inline-block mr-1"
802 this.handleTransferCommunity
808 class="pointer d-inline-block"
811 this.handleCancelShowConfirmTransferCommunity
820 {/* Admins can ban from all, and appoint other admins */}
824 <li className="list-inline-item">
828 onClick={linkEvent(this, this.handleModBanShow)}
830 {i18n.t('ban_from_site')}
835 onClick={linkEvent(this, this.handleModBanSubmit)}
837 {i18n.t('unban_from_site')}
842 {!post.banned && post.creator_local && (
843 <li className="list-inline-item">
846 onClick={linkEvent(this, this.handleAddAdmin)}
849 ? i18n.t('remove_as_admin')
850 : i18n.t('appoint_as_admin')}
856 {/* Site Creator can transfer to another admin */}
857 {this.amSiteCreator && this.isAdmin && (
858 <li className="list-inline-item">
859 {!this.state.showConfirmTransferSite ? (
864 this.handleShowConfirmTransferSite
867 {i18n.t('transfer_site')}
871 <span class="d-inline-block mr-1">
872 {i18n.t('are_you_sure')}
875 class="pointer d-inline-block mr-1"
876 onClick={linkEvent(this, this.handleTransferSite)}
881 class="pointer d-inline-block"
884 this.handleCancelShowConfirmTransferSite
901 removeAndBanDialogs() {
902 let post = this.props.post;
905 {this.state.showRemoveDialog && (
908 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
912 class="form-control mr-2"
913 placeholder={i18n.t('reason')}
914 value={this.state.removeReason}
915 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
917 <button type="submit" class="btn btn-secondary">
918 {i18n.t('remove_post')}
922 {this.state.showBanDialog && (
923 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
924 <div class="form-group row">
925 <label class="col-form-label" htmlFor="post-listing-reason">
930 id="post-listing-reason"
931 class="form-control mr-2"
932 placeholder={i18n.t('reason')}
933 value={this.state.banReason}
934 onInput={linkEvent(this, this.handleModBanReasonChange)}
936 <div class="form-group">
937 <div class="form-check">
939 class="form-check-input"
940 id="mod-ban-remove-data"
942 checked={this.state.removeData}
943 onChange={linkEvent(this, this.handleModRemoveDataChange)}
945 <label class="form-check-label" htmlFor="mod-ban-remove-data">
946 {i18n.t('remove_posts_comments')}
951 {/* TODO hold off on expires until later */}
952 {/* <div class="form-group row"> */}
953 {/* <label class="col-form-label">Expires</label> */}
954 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
956 <div class="form-group row">
957 <button type="submit" class="btn btn-secondary">
958 {i18n.t('ban')} {post.creator_name}
968 return this.props.post.thumbnail_url || isImage(this.props.post.url) ? (
970 <div className={`${this.state.imageExpanded ? 'col-12' : 'col-8'}`}>
971 {this.postTitleLine()}
974 {/* Post body prev or thumbnail */}
975 {!this.state.imageExpanded && this.thumbnail()}
983 showMobilePreview() {
985 this.props.post.body &&
986 !this.props.showBody && (
988 className="md-div mb-1"
989 dangerouslySetInnerHTML={{
990 __html: md.render(previewLines(this.props.post.body)),
1000 {/* The mobile view*/}
1001 <div class="d-block d-sm-none">
1003 <div class="col-12">
1004 {this.createdLine()}
1006 {/* If it has a thumbnail, do a right aligned thumbnail */}
1007 {this.mobileThumbnail()}
1009 {/* Show a preview of the post body */}
1010 {this.showMobilePreview()}
1012 {this.commentsLine(true)}
1013 {this.duplicatesLine()}
1014 {this.postActions()}
1015 {this.removeAndBanDialogs()}
1020 {/* The larger view*/}
1021 <div class="d-none d-sm-block">
1024 {!this.state.imageExpanded && (
1025 <div class="col-sm-2 pr-0">
1026 <div class="">{this.thumbnail()}</div>
1031 this.state.imageExpanded ? 'col-12' : 'col-12 col-sm-9'
1035 <div className="col-12">
1036 {this.postTitleLine()}
1037 {this.createdLine()}
1038 {this.commentsLine()}
1039 {this.duplicatesLine()}
1040 {this.postActions()}
1041 {this.removeAndBanDialogs()}
1051 private get myPost(): boolean {
1053 UserService.Instance.user &&
1054 this.props.post.creator_id == UserService.Instance.user.id
1058 get isMod(): boolean {
1060 this.props.moderators &&
1062 this.props.moderators.map(m => m.user_id),
1063 this.props.post.creator_id
1068 get isAdmin(): boolean {
1070 this.props.admins &&
1072 this.props.admins.map(a => a.id),
1073 this.props.post.creator_id
1078 get canMod(): 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
1094 get canModOnSelf(): boolean {
1095 if (this.props.admins && this.props.moderators) {
1096 let adminsThenMods = this.props.admins
1098 .concat(this.props.moderators.map(m => m.user_id));
1101 UserService.Instance.user,
1103 this.props.post.creator_id,
1111 get canAdmin(): boolean {
1113 this.props.admins &&
1115 UserService.Instance.user,
1116 this.props.admins.map(a => a.id),
1117 this.props.post.creator_id
1122 get amCommunityCreator(): boolean {
1124 this.props.moderators &&
1125 UserService.Instance.user &&
1126 this.props.post.creator_id != UserService.Instance.user.id &&
1127 UserService.Instance.user.id == this.props.moderators[0].user_id
1131 get amSiteCreator(): boolean {
1133 this.props.admins &&
1134 UserService.Instance.user &&
1135 this.props.post.creator_id != UserService.Instance.user.id &&
1136 UserService.Instance.user.id == this.props.admins[0].id
1140 handlePostLike(i: PostListing) {
1141 if (!UserService.Instance.user) {
1142 this.context.router.history.push(`/login`);
1145 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1147 if (i.state.my_vote == 1) {
1150 } else if (i.state.my_vote == -1) {
1151 i.state.downvotes--;
1159 i.state.my_vote = new_vote;
1161 let form: CreatePostLikeForm = {
1162 post_id: i.props.post.id,
1163 score: i.state.my_vote,
1166 WebSocketService.Instance.likePost(form);
1167 i.setState(i.state);
1171 handlePostDisLike(i: PostListing) {
1172 if (!UserService.Instance.user) {
1173 this.context.router.history.push(`/login`);
1176 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1178 if (i.state.my_vote == 1) {
1181 i.state.downvotes++;
1182 } else if (i.state.my_vote == -1) {
1183 i.state.downvotes--;
1186 i.state.downvotes++;
1190 i.state.my_vote = new_vote;
1192 let form: CreatePostLikeForm = {
1193 post_id: i.props.post.id,
1194 score: i.state.my_vote,
1197 WebSocketService.Instance.likePost(form);
1198 i.setState(i.state);
1202 handleEditClick(i: PostListing) {
1203 i.state.showEdit = true;
1204 i.setState(i.state);
1207 handleEditCancel() {
1208 this.state.showEdit = false;
1209 this.setState(this.state);
1212 // The actual editing is done in the recieve for post
1214 this.state.showEdit = false;
1215 this.setState(this.state);
1218 handleDeleteClick(i: PostListing) {
1219 let deleteForm: DeletePostForm = {
1220 edit_id: i.props.post.id,
1221 deleted: !i.props.post.deleted,
1224 WebSocketService.Instance.deletePost(deleteForm);
1227 handleSavePostClick(i: PostListing) {
1228 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1229 let form: SavePostForm = {
1230 post_id: i.props.post.id,
1234 WebSocketService.Instance.savePost(form);
1237 get crossPostParams(): string {
1238 let params = `?title=${this.props.post.name}`;
1239 let post = this.props.post;
1242 params += `&url=${encodeURIComponent(post.url)}`;
1244 if (this.props.post.body) {
1245 params += `&body=${this.props.post.body}`;
1250 handleModRemoveShow(i: PostListing) {
1251 i.state.showRemoveDialog = true;
1252 i.setState(i.state);
1255 handleModRemoveReasonChange(i: PostListing, event: any) {
1256 i.state.removeReason = event.target.value;
1257 i.setState(i.state);
1260 handleModRemoveDataChange(i: PostListing, event: any) {
1261 i.state.removeData = event.target.checked;
1262 i.setState(i.state);
1265 handleModRemoveSubmit(i: PostListing) {
1266 event.preventDefault();
1267 let form: RemovePostForm = {
1268 edit_id: i.props.post.id,
1269 removed: !i.props.post.removed,
1270 reason: i.state.removeReason,
1273 WebSocketService.Instance.removePost(form);
1275 i.state.showRemoveDialog = false;
1276 i.setState(i.state);
1279 handleModLock(i: PostListing) {
1280 let form: LockPostForm = {
1281 edit_id: i.props.post.id,
1282 locked: !i.props.post.locked,
1285 WebSocketService.Instance.lockPost(form);
1288 handleModSticky(i: PostListing) {
1289 let form: StickyPostForm = {
1290 edit_id: i.props.post.id,
1291 stickied: !i.props.post.stickied,
1294 WebSocketService.Instance.stickyPost(form);
1297 handleModBanFromCommunityShow(i: PostListing) {
1298 i.state.showBanDialog = true;
1299 i.state.banType = BanType.Community;
1300 i.setState(i.state);
1303 handleModBanShow(i: PostListing) {
1304 i.state.showBanDialog = true;
1305 i.state.banType = BanType.Site;
1306 i.setState(i.state);
1309 handleModBanReasonChange(i: PostListing, event: any) {
1310 i.state.banReason = event.target.value;
1311 i.setState(i.state);
1314 handleModBanExpiresChange(i: PostListing, event: any) {
1315 i.state.banExpires = event.target.value;
1316 i.setState(i.state);
1319 handleModBanFromCommunitySubmit(i: PostListing) {
1320 i.state.banType = BanType.Community;
1321 i.setState(i.state);
1322 i.handleModBanBothSubmit(i);
1325 handleModBanSubmit(i: PostListing) {
1326 i.state.banType = BanType.Site;
1327 i.setState(i.state);
1328 i.handleModBanBothSubmit(i);
1331 handleModBanBothSubmit(i: PostListing) {
1332 event.preventDefault();
1334 if (i.state.banType == BanType.Community) {
1335 // If its an unban, restore all their data
1336 let ban = !i.props.post.banned_from_community;
1338 i.state.removeData = false;
1340 let form: BanFromCommunityForm = {
1341 user_id: i.props.post.creator_id,
1342 community_id: i.props.post.community_id,
1344 remove_data: i.state.removeData,
1345 reason: i.state.banReason,
1346 expires: getUnixTime(i.state.banExpires),
1348 WebSocketService.Instance.banFromCommunity(form);
1350 // If its an unban, restore all their data
1351 let ban = !i.props.post.banned;
1353 i.state.removeData = false;
1355 let form: BanUserForm = {
1356 user_id: i.props.post.creator_id,
1358 remove_data: i.state.removeData,
1359 reason: i.state.banReason,
1360 expires: getUnixTime(i.state.banExpires),
1362 WebSocketService.Instance.banUser(form);
1365 i.state.showBanDialog = false;
1366 i.setState(i.state);
1369 handleAddModToCommunity(i: PostListing) {
1370 let form: AddModToCommunityForm = {
1371 user_id: i.props.post.creator_id,
1372 community_id: i.props.post.community_id,
1375 WebSocketService.Instance.addModToCommunity(form);
1376 i.setState(i.state);
1379 handleAddAdmin(i: PostListing) {
1380 let form: AddAdminForm = {
1381 user_id: i.props.post.creator_id,
1384 WebSocketService.Instance.addAdmin(form);
1385 i.setState(i.state);
1388 handleShowConfirmTransferCommunity(i: PostListing) {
1389 i.state.showConfirmTransferCommunity = true;
1390 i.setState(i.state);
1393 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1394 i.state.showConfirmTransferCommunity = false;
1395 i.setState(i.state);
1398 handleTransferCommunity(i: PostListing) {
1399 let form: TransferCommunityForm = {
1400 community_id: i.props.post.community_id,
1401 user_id: i.props.post.creator_id,
1403 WebSocketService.Instance.transferCommunity(form);
1404 i.state.showConfirmTransferCommunity = false;
1405 i.setState(i.state);
1408 handleShowConfirmTransferSite(i: PostListing) {
1409 i.state.showConfirmTransferSite = true;
1410 i.setState(i.state);
1413 handleCancelShowConfirmTransferSite(i: PostListing) {
1414 i.state.showConfirmTransferSite = false;
1415 i.setState(i.state);
1418 handleTransferSite(i: PostListing) {
1419 let form: TransferSiteForm = {
1420 user_id: i.props.post.creator_id,
1422 WebSocketService.Instance.transferSite(form);
1423 i.state.showConfirmTransferSite = false;
1424 i.setState(i.state);
1427 handleImageExpandClick(i: PostListing) {
1428 i.state.imageExpanded = !i.state.imageExpanded;
1429 i.setState(i.state);
1432 handleViewSource(i: PostListing) {
1433 i.state.viewSource = !i.state.viewSource;
1434 i.setState(i.state);
1437 handleShowAdvanced(i: PostListing) {
1438 i.state.showAdvanced = !i.state.showAdvanced;
1439 i.setState(i.state);
1443 get pointsTippy(): string {
1444 let points = i18n.t('number_of_points', {
1445 count: this.state.score,
1448 let upvotes = i18n.t('number_of_upvotes', {
1449 count: this.state.upvotes,
1452 let downvotes = i18n.t('number_of_downvotes', {
1453 count: this.state.downvotes,
1456 return `${points} • ${upvotes} • ${downvotes}`;