1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { WebSocketService, UserService } from '../services';
16 AddModToCommunityForm,
19 TransferCommunityForm,
21 } from 'lemmy-js-client';
22 import { BanType } from '../interfaces';
23 import { MomentTime } from './moment-time';
24 import { PostForm } from './post-form';
25 import { IFramelyCard } from './iframely-card';
26 import { UserListing } from './user-listing';
27 import { CommunityLink } from './community-link';
41 import { i18n } from '../i18next';
43 interface PostListingState {
45 showRemoveDialog: boolean;
47 showBanDialog: boolean;
52 showConfirmTransferSite: boolean;
53 showConfirmTransferCommunity: boolean;
54 imageExpanded: boolean;
56 showAdvanced: boolean;
63 interface PostListingProps {
65 communities: Community[];
66 showCommunity?: boolean;
68 moderators?: CommunityUser[];
70 enableDownvotes: boolean;
74 export class PostListing extends Component<PostListingProps, PostListingState> {
75 private emptyState: PostListingState = {
77 showRemoveDialog: false,
83 banType: BanType.Community,
84 showConfirmTransferSite: false,
85 showConfirmTransferCommunity: false,
89 my_vote: this.props.post.my_vote,
90 score: this.props.post.score,
91 upvotes: this.props.post.upvotes,
92 downvotes: this.props.post.downvotes,
95 constructor(props: any, context: any) {
96 super(props, context);
98 this.state = this.emptyState;
99 this.handlePostLike = this.handlePostLike.bind(this);
100 this.handlePostDisLike = this.handlePostDisLike.bind(this);
101 this.handleEditPost = this.handleEditPost.bind(this);
102 this.handleEditCancel = this.handleEditCancel.bind(this);
105 componentWillReceiveProps(nextProps: PostListingProps) {
106 this.state.my_vote = nextProps.post.my_vote;
107 this.state.upvotes = nextProps.post.upvotes;
108 this.state.downvotes = nextProps.post.downvotes;
109 this.state.score = nextProps.post.score;
110 if (this.props.post.id !== nextProps.post.id) {
111 this.state.imageExpanded = false;
113 this.setState(this.state);
119 {!this.state.showEdit ? (
127 post={this.props.post}
128 onEdit={this.handleEditPost}
129 onCancel={this.handleEditCancel}
130 enableNsfw={this.props.enableNsfw}
131 enableDownvotes={this.props.enableDownvotes}
132 communities={this.props.communities}
144 {this.props.post.url &&
145 this.props.showBody &&
146 this.props.post.embed_title && (
147 <IFramelyCard post={this.props.post} />
149 {this.props.showBody && this.props.post.body && (
151 {this.state.viewSource ? (
152 <pre>{this.props.post.body}</pre>
156 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
166 imgThumb(src: string) {
167 let post = this.props.post;
170 className={`img-fluid thumbnail rounded ${
171 post.nsfw || post.community_nsfw ? 'img-blur' : ''
178 getImage(thumbnail: boolean = false) {
179 let post = this.props.post;
180 if (isImage(post.url)) {
181 if (post.url.includes('pictrs')) {
182 return pictrsImage(post.url, thumbnail);
183 } else if (post.thumbnail_url) {
184 return pictrsImage(post.thumbnail_url, thumbnail);
188 } else if (post.thumbnail_url) {
189 return pictrsImage(post.thumbnail_url, thumbnail);
196 let post = this.props.post;
198 if (isImage(post.url)) {
201 class="float-right text-body pointer d-inline-block position-relative"
202 data-tippy-content={i18n.t('expand_here')}
203 onClick={linkEvent(this, this.handleImageExpandClick)}
205 {this.imgThumb(this.getImage(true))}
206 <svg class="icon mini-overlay">
207 <use xlinkHref="#icon-image"></use>
211 } else if (post.thumbnail_url) {
214 class="float-right text-body d-inline-block position-relative"
220 {this.imgThumb(this.getImage(true))}
221 <svg class="icon mini-overlay">
222 <use xlinkHref="#icon-external-link"></use>
226 } else if (post.url) {
227 if (isVideo(post.url)) {
229 <div class="embed-responsive embed-responsive-16by9">
235 class="embed-responsive-item"
237 <source src={post.url} type="video/mp4" />
244 className="text-body"
250 <div class="thumbnail rounded bg-light d-flex justify-content-center">
251 <svg class="icon d-flex align-items-center">
252 <use xlinkHref="#icon-external-link"></use>
261 className="text-body"
262 to={`/post/${post.id}`}
263 title={i18n.t('comments')}
265 <div class="thumbnail rounded bg-light d-flex justify-content-center">
266 <svg class="icon d-flex align-items-center">
267 <use xlinkHref="#icon-message-square"></use>
276 let post = this.props.post;
278 <ul class="list-inline mb-1 text-muted small">
279 <li className="list-inline-item">
282 name: post.creator_name,
283 preferred_username: post.creator_preferred_username,
284 avatar: post.creator_avatar,
286 local: post.creator_local,
287 actor_id: post.creator_actor_id,
288 published: post.creator_published,
293 <span className="mx-1 badge badge-light">{i18n.t('mod')}</span>
296 <span className="mx-1 badge badge-light">{i18n.t('admin')}</span>
298 {(post.banned_from_community || post.banned) && (
299 <span className="mx-1 badge badge-danger">{i18n.t('banned')}</span>
301 {this.props.showCommunity && (
303 <span class="mx-1"> {i18n.t('to')} </span>
306 name: post.community_name,
307 id: post.community_id,
308 local: post.community_local,
309 actor_id: post.community_actor_id,
310 icon: post.community_icon,
316 <li className="list-inline-item">•</li>
317 {post.url && !(hostname(post.url) == window.location.hostname) && (
319 <li className="list-inline-item">
321 className="text-muted font-italic"
330 <li className="list-inline-item">•</li>
333 <li className="list-inline-item">
335 <MomentTime data={post} />
340 <li className="list-inline-item">•</li>
341 <li className="list-inline-item">
342 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
344 className="text-muted"
345 data-tippy-content={md.render(previewLines(post.body))}
346 data-tippy-allowHtml={true}
347 to={`/post/${post.id}`}
349 <svg class="mr-1 icon icon-inline">
350 <use xlinkHref="#icon-book-open"></use>
362 <div className={`vote-bar col-1 pr-0 small text-center`}>
364 className={`btn-animate btn btn-link p-0 ${
365 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
367 onClick={linkEvent(this, this.handlePostLike)}
368 data-tippy-content={i18n.t('upvote')}
370 <svg class="icon upvote">
371 <use xlinkHref="#icon-arrow-up1"></use>
375 class={`unselectable pointer font-weight-bold text-muted px-1`}
376 data-tippy-content={this.pointsTippy}
380 {this.props.enableDownvotes && (
382 className={`btn-animate btn btn-link p-0 ${
383 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
385 onClick={linkEvent(this, this.handlePostDisLike)}
386 data-tippy-content={i18n.t('downvote')}
388 <svg class="icon downvote">
389 <use xlinkHref="#icon-arrow-down1"></use>
398 let post = this.props.post;
400 <div className="post-title overflow-hidden">
402 {this.props.showBody && post.url ? (
404 className={!post.stickied ? 'text-body' : 'text-primary'}
414 className={!post.stickied ? 'text-body' : 'text-primary'}
415 to={`/post/${post.id}`}
416 title={i18n.t('comments')}
421 {(isImage(post.url) || this.props.post.thumbnail_url) && (
423 {!this.state.imageExpanded ? (
425 class="text-monospace unselectable pointer ml-2 text-muted small"
426 data-tippy-content={i18n.t('expand_here')}
427 onClick={linkEvent(this, this.handleImageExpandClick)}
429 <svg class="icon icon-inline">
430 <use xlinkHref="#icon-plus-square"></use>
436 class="text-monospace unselectable pointer ml-2 text-muted small"
437 onClick={linkEvent(this, this.handleImageExpandClick)}
439 <svg class="icon icon-inline">
440 <use xlinkHref="#icon-minus-square"></use>
446 onClick={linkEvent(this, this.handleImageExpandClick)}
449 class="img-fluid img-expanded"
450 src={this.getImage()}
459 <small className="ml-2 text-muted font-italic">
465 className="unselectable pointer ml-2 text-muted font-italic"
466 data-tippy-content={i18n.t('deleted')}
468 <svg class={`icon icon-inline text-danger`}>
469 <use xlinkHref="#icon-trash"></use>
475 className="unselectable pointer ml-2 text-muted font-italic"
476 data-tippy-content={i18n.t('locked')}
478 <svg class={`icon icon-inline text-danger`}>
479 <use xlinkHref="#icon-lock"></use>
485 className="unselectable pointer ml-2 text-muted font-italic"
486 data-tippy-content={i18n.t('stickied')}
488 <svg class={`icon icon-inline text-primary`}>
489 <use xlinkHref="#icon-pin"></use>
494 <small className="ml-2 text-muted font-italic">
503 commentsLine(showVotes: boolean = false) {
504 let post = this.props.post;
506 <ul class="d-flex align-items-center list-inline mb-1 text-muted small">
507 <li className="list-inline-item">
509 className="text-muted"
510 title={i18n.t('number_of_comments', {
511 count: post.number_of_comments,
513 to={`/post/${post.id}`}
515 <svg class="mr-1 icon icon-inline">
516 <use xlinkHref="#icon-message-square"></use>
518 {i18n.t('number_of_comments', {
519 count: post.number_of_comments,
523 {(showVotes || this.state.upvotes !== this.state.score) && (
526 class="unselectable pointer ml-3"
527 data-tippy-content={this.pointsTippy}
529 <li className="list-inline-item">
531 className={`btn-animate btn btn-link p-0 ${
532 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
534 onClick={linkEvent(this, this.handlePostLike)}
536 <svg class="small icon icon-inline mx-1">
537 <use xlinkHref="#icon-arrow-up1"></use>
542 <li className="list-inline-item">
544 className={`btn-animate btn btn-link p-0 ${
545 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
547 onClick={linkEvent(this, this.handlePostDisLike)}
549 <svg class="small icon icon-inline mx-1">
550 <use xlinkHref="#icon-arrow-down1"></use>
552 {this.state.downvotes}
564 this.props.post.duplicates && (
565 <ul class="list-inline mb-1 small text-muted">
567 <li className="list-inline-item mr-2">
568 {i18n.t('cross_posted_to')}
570 {this.props.post.duplicates.map(post => (
571 <li className="list-inline-item mr-2">
572 <Link to={`/post/${post.id}`}>{post.community_name}</Link>
582 let post = this.props.post;
584 <ul class="list-inline mb-1 text-muted font-weight-bold">
585 {UserService.Instance.user && (
587 {this.props.showBody && (
589 <li className="list-inline-item">
591 class="btn btn-link btn-animate text-muted"
592 onClick={linkEvent(this, this.handleSavePostClick)}
594 post.saved ? i18n.t('unsave') : i18n.t('save')
598 class={`icon icon-inline ${post.saved && 'text-warning'}`}
600 <use xlinkHref="#icon-star"></use>
604 <li className="list-inline-item">
606 className="btn btn-link btn-animate text-muted"
607 to={`/create_post${this.crossPostParams}`}
608 title={i18n.t('cross_post')}
610 <svg class="icon icon-inline">
611 <use xlinkHref="#icon-copy"></use>
617 {this.myPost && this.props.showBody && (
619 <li className="list-inline-item">
621 class="btn btn-link btn-animate text-muted"
622 onClick={linkEvent(this, this.handleEditClick)}
623 data-tippy-content={i18n.t('edit')}
625 <svg class="icon icon-inline">
626 <use xlinkHref="#icon-edit"></use>
630 <li className="list-inline-item">
632 class="btn btn-link btn-animate text-muted"
633 onClick={linkEvent(this, this.handleDeleteClick)}
635 !post.deleted ? i18n.t('delete') : i18n.t('restore')
639 class={`icon icon-inline ${
640 post.deleted && 'text-danger'
643 <use xlinkHref="#icon-trash"></use>
650 {!this.state.showAdvanced && this.props.showBody ? (
651 <li className="list-inline-item">
653 class="btn btn-link btn-animate text-muted"
654 onClick={linkEvent(this, this.handleShowAdvanced)}
655 data-tippy-content={i18n.t('more')}
657 <svg class="icon icon-inline">
658 <use xlinkHref="#icon-more-vertical"></use>
664 {this.props.showBody && post.body && (
665 <li className="list-inline-item">
667 class="btn btn-link btn-animate text-muted"
668 onClick={linkEvent(this, this.handleViewSource)}
669 data-tippy-content={i18n.t('view_source')}
672 class={`icon icon-inline ${
673 this.state.viewSource && 'text-success'
676 <use xlinkHref="#icon-file-text"></use>
681 {this.canModOnSelf && (
683 <li className="list-inline-item">
685 class="btn btn-link btn-animate text-muted"
686 onClick={linkEvent(this, this.handleModLock)}
688 post.locked ? i18n.t('unlock') : i18n.t('lock')
692 class={`icon icon-inline ${
693 post.locked && 'text-danger'
696 <use xlinkHref="#icon-lock"></use>
700 <li className="list-inline-item">
702 class="btn btn-link btn-animate text-muted"
703 onClick={linkEvent(this, this.handleModSticky)}
705 post.stickied ? i18n.t('unsticky') : i18n.t('sticky')
709 class={`icon icon-inline ${
710 post.stickied && 'text-success'
713 <use xlinkHref="#icon-pin"></use>
719 {/* Mods can ban from community, and appoint as mods to community */}
720 {(this.canMod || this.canAdmin) && (
721 <li className="list-inline-item">
725 onClick={linkEvent(this, this.handleModRemoveShow)}
732 onClick={linkEvent(this, this.handleModRemoveSubmit)}
742 <li className="list-inline-item">
743 {!post.banned_from_community ? (
748 this.handleModBanFromCommunityShow
758 this.handleModBanFromCommunitySubmit
766 {!post.banned_from_community && post.creator_local && (
767 <li className="list-inline-item">
772 this.handleAddModToCommunity
776 ? i18n.t('remove_as_mod')
777 : i18n.t('appoint_as_mod')}
783 {/* Community creators and admins can transfer community to another mod */}
784 {(this.amCommunityCreator || this.canAdmin) &&
786 post.creator_local && (
787 <li className="list-inline-item">
788 {!this.state.showConfirmTransferCommunity ? (
793 this.handleShowConfirmTransferCommunity
796 {i18n.t('transfer_community')}
800 <span class="d-inline-block mr-1">
801 {i18n.t('are_you_sure')}
804 class="pointer d-inline-block mr-1"
807 this.handleTransferCommunity
813 class="pointer d-inline-block"
816 this.handleCancelShowConfirmTransferCommunity
825 {/* Admins can ban from all, and appoint other admins */}
829 <li className="list-inline-item">
833 onClick={linkEvent(this, this.handleModBanShow)}
835 {i18n.t('ban_from_site')}
840 onClick={linkEvent(this, this.handleModBanSubmit)}
842 {i18n.t('unban_from_site')}
847 {!post.banned && post.creator_local && (
848 <li className="list-inline-item">
851 onClick={linkEvent(this, this.handleAddAdmin)}
854 ? i18n.t('remove_as_admin')
855 : i18n.t('appoint_as_admin')}
861 {/* Site Creator can transfer to another admin */}
862 {this.amSiteCreator && this.isAdmin && (
863 <li className="list-inline-item">
864 {!this.state.showConfirmTransferSite ? (
869 this.handleShowConfirmTransferSite
872 {i18n.t('transfer_site')}
876 <span class="d-inline-block mr-1">
877 {i18n.t('are_you_sure')}
880 class="pointer d-inline-block mr-1"
881 onClick={linkEvent(this, this.handleTransferSite)}
886 class="pointer d-inline-block"
889 this.handleCancelShowConfirmTransferSite
906 removeAndBanDialogs() {
907 let post = this.props.post;
910 {this.state.showRemoveDialog && (
913 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
917 class="form-control mr-2"
918 placeholder={i18n.t('reason')}
919 value={this.state.removeReason}
920 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
922 <button type="submit" class="btn btn-secondary">
923 {i18n.t('remove_post')}
927 {this.state.showBanDialog && (
928 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
929 <div class="form-group row">
930 <label class="col-form-label" htmlFor="post-listing-reason">
935 id="post-listing-reason"
936 class="form-control mr-2"
937 placeholder={i18n.t('reason')}
938 value={this.state.banReason}
939 onInput={linkEvent(this, this.handleModBanReasonChange)}
941 <div class="form-group">
942 <div class="form-check">
944 class="form-check-input"
945 id="mod-ban-remove-data"
947 checked={this.state.removeData}
948 onChange={linkEvent(this, this.handleModRemoveDataChange)}
950 <label class="form-check-label" htmlFor="mod-ban-remove-data">
951 {i18n.t('remove_posts_comments')}
956 {/* TODO hold off on expires until later */}
957 {/* <div class="form-group row"> */}
958 {/* <label class="col-form-label">Expires</label> */}
959 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
961 <div class="form-group row">
962 <button type="submit" class="btn btn-secondary">
963 {i18n.t('ban')} {post.creator_name}
973 return this.props.post.thumbnail_url || isImage(this.props.post.url) ? (
975 <div className={`${this.state.imageExpanded ? 'col-12' : 'col-8'}`}>
976 {this.postTitleLine()}
979 {/* Post body prev or thumbnail */}
980 {!this.state.imageExpanded && this.thumbnail()}
988 showMobilePreview() {
990 this.props.post.body &&
991 !this.props.showBody && (
993 className="md-div mb-1"
994 dangerouslySetInnerHTML={{
995 __html: md.render(previewLines(this.props.post.body)),
1005 {/* The mobile view*/}
1006 <div class="d-block d-sm-none">
1008 <div class="col-12">
1009 {this.createdLine()}
1011 {/* If it has a thumbnail, do a right aligned thumbnail */}
1012 {this.mobileThumbnail()}
1014 {/* Show a preview of the post body */}
1015 {this.showMobilePreview()}
1017 {this.commentsLine(true)}
1018 {this.duplicatesLine()}
1019 {this.postActions()}
1020 {this.removeAndBanDialogs()}
1025 {/* The larger view*/}
1026 <div class="d-none d-sm-block">
1029 {!this.state.imageExpanded && (
1030 <div class="col-sm-2 pr-0">
1031 <div class="">{this.thumbnail()}</div>
1036 this.state.imageExpanded ? 'col-12' : 'col-12 col-sm-9'
1040 <div className="col-12">
1041 {this.postTitleLine()}
1042 {this.createdLine()}
1043 {this.commentsLine()}
1044 {this.duplicatesLine()}
1045 {this.postActions()}
1046 {this.removeAndBanDialogs()}
1056 private get myPost(): boolean {
1058 UserService.Instance.user &&
1059 this.props.post.creator_id == UserService.Instance.user.id
1063 get isMod(): boolean {
1065 this.props.moderators &&
1067 this.props.moderators.map(m => m.user_id),
1068 this.props.post.creator_id
1073 get isAdmin(): boolean {
1075 this.props.admins &&
1077 this.props.admins.map(a => a.id),
1078 this.props.post.creator_id
1083 get canMod(): boolean {
1084 if (this.props.admins && this.props.moderators) {
1085 let adminsThenMods = this.props.admins
1087 .concat(this.props.moderators.map(m => m.user_id));
1090 UserService.Instance.user,
1092 this.props.post.creator_id
1099 get canModOnSelf(): boolean {
1100 if (this.props.admins && this.props.moderators) {
1101 let adminsThenMods = this.props.admins
1103 .concat(this.props.moderators.map(m => m.user_id));
1106 UserService.Instance.user,
1108 this.props.post.creator_id,
1116 get canAdmin(): boolean {
1118 this.props.admins &&
1120 UserService.Instance.user,
1121 this.props.admins.map(a => a.id),
1122 this.props.post.creator_id
1127 get amCommunityCreator(): boolean {
1129 this.props.moderators &&
1130 UserService.Instance.user &&
1131 this.props.post.creator_id != UserService.Instance.user.id &&
1132 UserService.Instance.user.id == this.props.moderators[0].user_id
1136 get amSiteCreator(): boolean {
1138 this.props.admins &&
1139 UserService.Instance.user &&
1140 this.props.post.creator_id != UserService.Instance.user.id &&
1141 UserService.Instance.user.id == this.props.admins[0].id
1145 handlePostLike(i: PostListing) {
1146 if (!UserService.Instance.user) {
1147 this.context.router.history.push(`/login`);
1150 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1152 if (i.state.my_vote == 1) {
1155 } else if (i.state.my_vote == -1) {
1156 i.state.downvotes--;
1164 i.state.my_vote = new_vote;
1166 let form: CreatePostLikeForm = {
1167 post_id: i.props.post.id,
1168 score: i.state.my_vote,
1171 WebSocketService.Instance.likePost(form);
1172 i.setState(i.state);
1176 handlePostDisLike(i: PostListing) {
1177 if (!UserService.Instance.user) {
1178 this.context.router.history.push(`/login`);
1181 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1183 if (i.state.my_vote == 1) {
1186 i.state.downvotes++;
1187 } else if (i.state.my_vote == -1) {
1188 i.state.downvotes--;
1191 i.state.downvotes++;
1195 i.state.my_vote = new_vote;
1197 let form: CreatePostLikeForm = {
1198 post_id: i.props.post.id,
1199 score: i.state.my_vote,
1202 WebSocketService.Instance.likePost(form);
1203 i.setState(i.state);
1207 handleEditClick(i: PostListing) {
1208 i.state.showEdit = true;
1209 i.setState(i.state);
1212 handleEditCancel() {
1213 this.state.showEdit = false;
1214 this.setState(this.state);
1217 // The actual editing is done in the recieve for post
1219 this.state.showEdit = false;
1220 this.setState(this.state);
1223 handleDeleteClick(i: PostListing) {
1224 let deleteForm: DeletePostForm = {
1225 edit_id: i.props.post.id,
1226 deleted: !i.props.post.deleted,
1229 WebSocketService.Instance.deletePost(deleteForm);
1232 handleSavePostClick(i: PostListing) {
1233 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1234 let form: SavePostForm = {
1235 post_id: i.props.post.id,
1239 WebSocketService.Instance.savePost(form);
1242 get crossPostParams(): string {
1243 let params = `?title=${this.props.post.name}`;
1244 let post = this.props.post;
1247 params += `&url=${encodeURIComponent(post.url)}`;
1249 if (this.props.post.body) {
1250 params += `&body=${this.props.post.body}`;
1255 handleModRemoveShow(i: PostListing) {
1256 i.state.showRemoveDialog = true;
1257 i.setState(i.state);
1260 handleModRemoveReasonChange(i: PostListing, event: any) {
1261 i.state.removeReason = event.target.value;
1262 i.setState(i.state);
1265 handleModRemoveDataChange(i: PostListing, event: any) {
1266 i.state.removeData = event.target.checked;
1267 i.setState(i.state);
1270 handleModRemoveSubmit(i: PostListing) {
1271 event.preventDefault();
1272 let form: RemovePostForm = {
1273 edit_id: i.props.post.id,
1274 removed: !i.props.post.removed,
1275 reason: i.state.removeReason,
1278 WebSocketService.Instance.removePost(form);
1280 i.state.showRemoveDialog = false;
1281 i.setState(i.state);
1284 handleModLock(i: PostListing) {
1285 let form: LockPostForm = {
1286 edit_id: i.props.post.id,
1287 locked: !i.props.post.locked,
1290 WebSocketService.Instance.lockPost(form);
1293 handleModSticky(i: PostListing) {
1294 let form: StickyPostForm = {
1295 edit_id: i.props.post.id,
1296 stickied: !i.props.post.stickied,
1299 WebSocketService.Instance.stickyPost(form);
1302 handleModBanFromCommunityShow(i: PostListing) {
1303 i.state.showBanDialog = true;
1304 i.state.banType = BanType.Community;
1305 i.setState(i.state);
1308 handleModBanShow(i: PostListing) {
1309 i.state.showBanDialog = true;
1310 i.state.banType = BanType.Site;
1311 i.setState(i.state);
1314 handleModBanReasonChange(i: PostListing, event: any) {
1315 i.state.banReason = event.target.value;
1316 i.setState(i.state);
1319 handleModBanExpiresChange(i: PostListing, event: any) {
1320 i.state.banExpires = event.target.value;
1321 i.setState(i.state);
1324 handleModBanFromCommunitySubmit(i: PostListing) {
1325 i.state.banType = BanType.Community;
1326 i.setState(i.state);
1327 i.handleModBanBothSubmit(i);
1330 handleModBanSubmit(i: PostListing) {
1331 i.state.banType = BanType.Site;
1332 i.setState(i.state);
1333 i.handleModBanBothSubmit(i);
1336 handleModBanBothSubmit(i: PostListing) {
1337 event.preventDefault();
1339 if (i.state.banType == BanType.Community) {
1340 // If its an unban, restore all their data
1341 let ban = !i.props.post.banned_from_community;
1343 i.state.removeData = false;
1345 let form: BanFromCommunityForm = {
1346 user_id: i.props.post.creator_id,
1347 community_id: i.props.post.community_id,
1349 remove_data: i.state.removeData,
1350 reason: i.state.banReason,
1351 expires: getUnixTime(i.state.banExpires),
1353 WebSocketService.Instance.banFromCommunity(form);
1355 // If its an unban, restore all their data
1356 let ban = !i.props.post.banned;
1358 i.state.removeData = false;
1360 let form: BanUserForm = {
1361 user_id: i.props.post.creator_id,
1363 remove_data: i.state.removeData,
1364 reason: i.state.banReason,
1365 expires: getUnixTime(i.state.banExpires),
1367 WebSocketService.Instance.banUser(form);
1370 i.state.showBanDialog = false;
1371 i.setState(i.state);
1374 handleAddModToCommunity(i: PostListing) {
1375 let form: AddModToCommunityForm = {
1376 user_id: i.props.post.creator_id,
1377 community_id: i.props.post.community_id,
1380 WebSocketService.Instance.addModToCommunity(form);
1381 i.setState(i.state);
1384 handleAddAdmin(i: PostListing) {
1385 let form: AddAdminForm = {
1386 user_id: i.props.post.creator_id,
1389 WebSocketService.Instance.addAdmin(form);
1390 i.setState(i.state);
1393 handleShowConfirmTransferCommunity(i: PostListing) {
1394 i.state.showConfirmTransferCommunity = true;
1395 i.setState(i.state);
1398 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1399 i.state.showConfirmTransferCommunity = false;
1400 i.setState(i.state);
1403 handleTransferCommunity(i: PostListing) {
1404 let form: TransferCommunityForm = {
1405 community_id: i.props.post.community_id,
1406 user_id: i.props.post.creator_id,
1408 WebSocketService.Instance.transferCommunity(form);
1409 i.state.showConfirmTransferCommunity = false;
1410 i.setState(i.state);
1413 handleShowConfirmTransferSite(i: PostListing) {
1414 i.state.showConfirmTransferSite = true;
1415 i.setState(i.state);
1418 handleCancelShowConfirmTransferSite(i: PostListing) {
1419 i.state.showConfirmTransferSite = false;
1420 i.setState(i.state);
1423 handleTransferSite(i: PostListing) {
1424 let form: TransferSiteForm = {
1425 user_id: i.props.post.creator_id,
1427 WebSocketService.Instance.transferSite(form);
1428 i.state.showConfirmTransferSite = false;
1429 i.setState(i.state);
1432 handleImageExpandClick(i: PostListing) {
1433 i.state.imageExpanded = !i.state.imageExpanded;
1434 i.setState(i.state);
1437 handleViewSource(i: PostListing) {
1438 i.state.viewSource = !i.state.viewSource;
1439 i.setState(i.state);
1442 handleShowAdvanced(i: PostListing) {
1443 i.state.showAdvanced = !i.state.showAdvanced;
1444 i.setState(i.state);
1448 get pointsTippy(): string {
1449 let points = i18n.t('number_of_points', {
1450 count: this.state.score,
1453 let upvotes = i18n.t('number_of_upvotes', {
1454 count: this.state.upvotes,
1457 let downvotes = i18n.t('number_of_downvotes', {
1458 count: this.state.downvotes,
1461 return `${points} • ${upvotes} • ${downvotes}`;