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 && (
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) && this.isMod && (
778 <li className="list-inline-item">
779 {!this.state.showConfirmTransferCommunity ? (
784 this.handleShowConfirmTransferCommunity
787 {i18n.t('transfer_community')}
791 <span class="d-inline-block mr-1">
792 {i18n.t('are_you_sure')}
795 class="pointer d-inline-block mr-1"
798 this.handleTransferCommunity
804 class="pointer d-inline-block"
807 this.handleCancelShowConfirmTransferCommunity
816 {/* Admins can ban from all, and appoint other admins */}
820 <li className="list-inline-item">
824 onClick={linkEvent(this, this.handleModBanShow)}
826 {i18n.t('ban_from_site')}
831 onClick={linkEvent(this, this.handleModBanSubmit)}
833 {i18n.t('unban_from_site')}
839 <li className="list-inline-item">
842 onClick={linkEvent(this, this.handleAddAdmin)}
845 ? i18n.t('remove_as_admin')
846 : i18n.t('appoint_as_admin')}
852 {/* Site Creator can transfer to another admin */}
853 {this.amSiteCreator && this.isAdmin && (
854 <li className="list-inline-item">
855 {!this.state.showConfirmTransferSite ? (
860 this.handleShowConfirmTransferSite
863 {i18n.t('transfer_site')}
867 <span class="d-inline-block mr-1">
868 {i18n.t('are_you_sure')}
871 class="pointer d-inline-block mr-1"
872 onClick={linkEvent(this, this.handleTransferSite)}
877 class="pointer d-inline-block"
880 this.handleCancelShowConfirmTransferSite
897 removeAndBanDialogs() {
898 let post = this.props.post;
901 {this.state.showRemoveDialog && (
904 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
908 class="form-control mr-2"
909 placeholder={i18n.t('reason')}
910 value={this.state.removeReason}
911 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
913 <button type="submit" class="btn btn-secondary">
914 {i18n.t('remove_post')}
918 {this.state.showBanDialog && (
919 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
920 <div class="form-group row">
921 <label class="col-form-label" htmlFor="post-listing-reason">
926 id="post-listing-reason"
927 class="form-control mr-2"
928 placeholder={i18n.t('reason')}
929 value={this.state.banReason}
930 onInput={linkEvent(this, this.handleModBanReasonChange)}
933 {/* TODO hold off on expires until later */}
934 {/* <div class="form-group row"> */}
935 {/* <label class="col-form-label">Expires</label> */}
936 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
938 <div class="form-group row">
939 <button type="submit" class="btn btn-secondary">
940 {i18n.t('ban')} {post.creator_name}
950 return this.props.post.thumbnail_url || isImage(this.props.post.url) ? (
952 <div className={`${this.state.imageExpanded ? 'col-12' : 'col-8'}`}>
953 {this.postTitleLine()}
956 {/* Post body prev or thumbnail */}
957 {!this.state.imageExpanded && this.thumbnail()}
965 showMobilePreview() {
967 this.props.post.body &&
968 !this.props.showBody && (
970 className="md-div mb-1"
971 dangerouslySetInnerHTML={{
972 __html: md.render(previewLines(this.props.post.body)),
982 {/* The mobile view*/}
983 <div class="d-block d-sm-none">
988 {/* If it has a thumbnail, do a right aligned thumbnail */}
989 {this.mobileThumbnail()}
991 {/* Show a preview of the post body */}
992 {this.showMobilePreview()}
994 {this.commentsLine(true)}
995 {this.duplicatesLine()}
997 {this.removeAndBanDialogs()}
1002 {/* The larger view*/}
1003 <div class="d-none d-sm-block">
1006 {!this.state.imageExpanded && (
1007 <div class="col-sm-2 pr-0">
1008 <div class="">{this.thumbnail()}</div>
1013 this.state.imageExpanded ? 'col-12' : 'col-12 col-sm-9'
1017 <div className="col-12">
1018 {this.postTitleLine()}
1019 {this.createdLine()}
1020 {this.commentsLine()}
1021 {this.duplicatesLine()}
1022 {this.postActions()}
1023 {this.removeAndBanDialogs()}
1033 private get myPost(): boolean {
1035 UserService.Instance.user &&
1036 this.props.post.creator_id == UserService.Instance.user.id
1040 get isMod(): boolean {
1042 this.props.moderators &&
1044 this.props.moderators.map(m => m.user_id),
1045 this.props.post.creator_id
1050 get isAdmin(): boolean {
1052 this.props.admins &&
1054 this.props.admins.map(a => a.id),
1055 this.props.post.creator_id
1060 get canMod(): boolean {
1061 if (this.props.admins && this.props.moderators) {
1062 let adminsThenMods = this.props.admins
1064 .concat(this.props.moderators.map(m => m.user_id));
1067 UserService.Instance.user,
1069 this.props.post.creator_id
1076 get canModOnSelf(): boolean {
1077 if (this.props.admins && this.props.moderators) {
1078 let adminsThenMods = this.props.admins
1080 .concat(this.props.moderators.map(m => m.user_id));
1083 UserService.Instance.user,
1085 this.props.post.creator_id,
1093 get canAdmin(): boolean {
1095 this.props.admins &&
1097 UserService.Instance.user,
1098 this.props.admins.map(a => a.id),
1099 this.props.post.creator_id
1104 get amCommunityCreator(): boolean {
1106 this.props.moderators &&
1107 UserService.Instance.user &&
1108 this.props.post.creator_id != UserService.Instance.user.id &&
1109 UserService.Instance.user.id == this.props.moderators[0].user_id
1113 get amSiteCreator(): boolean {
1115 this.props.admins &&
1116 UserService.Instance.user &&
1117 this.props.post.creator_id != UserService.Instance.user.id &&
1118 UserService.Instance.user.id == this.props.admins[0].id
1122 handlePostLike(i: PostListing) {
1123 if (!UserService.Instance.user) {
1124 this.context.router.history.push(`/login`);
1127 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1129 if (i.state.my_vote == 1) {
1132 } else if (i.state.my_vote == -1) {
1133 i.state.downvotes--;
1141 i.state.my_vote = new_vote;
1143 let form: CreatePostLikeForm = {
1144 post_id: i.props.post.id,
1145 score: i.state.my_vote,
1148 WebSocketService.Instance.likePost(form);
1149 i.setState(i.state);
1153 handlePostDisLike(i: PostListing) {
1154 if (!UserService.Instance.user) {
1155 this.context.router.history.push(`/login`);
1158 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1160 if (i.state.my_vote == 1) {
1163 i.state.downvotes++;
1164 } else if (i.state.my_vote == -1) {
1165 i.state.downvotes--;
1168 i.state.downvotes++;
1172 i.state.my_vote = new_vote;
1174 let form: CreatePostLikeForm = {
1175 post_id: i.props.post.id,
1176 score: i.state.my_vote,
1179 WebSocketService.Instance.likePost(form);
1180 i.setState(i.state);
1184 handleEditClick(i: PostListing) {
1185 i.state.showEdit = true;
1186 i.setState(i.state);
1189 handleEditCancel() {
1190 this.state.showEdit = false;
1191 this.setState(this.state);
1194 // The actual editing is done in the recieve for post
1196 this.state.showEdit = false;
1197 this.setState(this.state);
1200 handleDeleteClick(i: PostListing) {
1201 let deleteForm: DeletePostForm = {
1202 edit_id: i.props.post.id,
1203 deleted: !i.props.post.deleted,
1206 WebSocketService.Instance.deletePost(deleteForm);
1209 handleSavePostClick(i: PostListing) {
1210 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1211 let form: SavePostForm = {
1212 post_id: i.props.post.id,
1216 WebSocketService.Instance.savePost(form);
1219 get crossPostParams(): string {
1220 let params = `?title=${this.props.post.name}`;
1221 let post = this.props.post;
1224 params += `&url=${post.url}`;
1226 if (this.props.post.body) {
1227 params += `&body=${this.props.post.body}`;
1232 handleModRemoveShow(i: PostListing) {
1233 i.state.showRemoveDialog = true;
1234 i.setState(i.state);
1237 handleModRemoveReasonChange(i: PostListing, event: any) {
1238 i.state.removeReason = event.target.value;
1239 i.setState(i.state);
1242 handleModRemoveSubmit(i: PostListing) {
1243 event.preventDefault();
1244 let form: RemovePostForm = {
1245 edit_id: i.props.post.id,
1246 removed: !i.props.post.removed,
1247 reason: i.state.removeReason,
1250 WebSocketService.Instance.removePost(form);
1252 i.state.showRemoveDialog = false;
1253 i.setState(i.state);
1256 handleModLock(i: PostListing) {
1257 let form: LockPostForm = {
1258 edit_id: i.props.post.id,
1259 locked: !i.props.post.locked,
1262 WebSocketService.Instance.lockPost(form);
1265 handleModSticky(i: PostListing) {
1266 let form: StickyPostForm = {
1267 edit_id: i.props.post.id,
1268 stickied: !i.props.post.stickied,
1271 WebSocketService.Instance.stickyPost(form);
1274 handleModBanFromCommunityShow(i: PostListing) {
1275 i.state.showBanDialog = true;
1276 i.state.banType = BanType.Community;
1277 i.setState(i.state);
1280 handleModBanShow(i: PostListing) {
1281 i.state.showBanDialog = true;
1282 i.state.banType = BanType.Site;
1283 i.setState(i.state);
1286 handleModBanReasonChange(i: PostListing, event: any) {
1287 i.state.banReason = event.target.value;
1288 i.setState(i.state);
1291 handleModBanExpiresChange(i: PostListing, event: any) {
1292 i.state.banExpires = event.target.value;
1293 i.setState(i.state);
1296 handleModBanFromCommunitySubmit(i: PostListing) {
1297 i.state.banType = BanType.Community;
1298 i.setState(i.state);
1299 i.handleModBanBothSubmit(i);
1302 handleModBanSubmit(i: PostListing) {
1303 i.state.banType = BanType.Site;
1304 i.setState(i.state);
1305 i.handleModBanBothSubmit(i);
1308 handleModBanBothSubmit(i: PostListing) {
1309 event.preventDefault();
1311 if (i.state.banType == BanType.Community) {
1312 let form: BanFromCommunityForm = {
1313 user_id: i.props.post.creator_id,
1314 community_id: i.props.post.community_id,
1315 ban: !i.props.post.banned_from_community,
1316 reason: i.state.banReason,
1317 expires: getUnixTime(i.state.banExpires),
1319 WebSocketService.Instance.banFromCommunity(form);
1321 let form: BanUserForm = {
1322 user_id: i.props.post.creator_id,
1323 ban: !i.props.post.banned,
1324 reason: i.state.banReason,
1325 expires: getUnixTime(i.state.banExpires),
1327 WebSocketService.Instance.banUser(form);
1330 i.state.showBanDialog = false;
1331 i.setState(i.state);
1334 handleAddModToCommunity(i: PostListing) {
1335 let form: AddModToCommunityForm = {
1336 user_id: i.props.post.creator_id,
1337 community_id: i.props.post.community_id,
1340 WebSocketService.Instance.addModToCommunity(form);
1341 i.setState(i.state);
1344 handleAddAdmin(i: PostListing) {
1345 let form: AddAdminForm = {
1346 user_id: i.props.post.creator_id,
1349 WebSocketService.Instance.addAdmin(form);
1350 i.setState(i.state);
1353 handleShowConfirmTransferCommunity(i: PostListing) {
1354 i.state.showConfirmTransferCommunity = true;
1355 i.setState(i.state);
1358 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1359 i.state.showConfirmTransferCommunity = false;
1360 i.setState(i.state);
1363 handleTransferCommunity(i: PostListing) {
1364 let form: TransferCommunityForm = {
1365 community_id: i.props.post.community_id,
1366 user_id: i.props.post.creator_id,
1368 WebSocketService.Instance.transferCommunity(form);
1369 i.state.showConfirmTransferCommunity = false;
1370 i.setState(i.state);
1373 handleShowConfirmTransferSite(i: PostListing) {
1374 i.state.showConfirmTransferSite = true;
1375 i.setState(i.state);
1378 handleCancelShowConfirmTransferSite(i: PostListing) {
1379 i.state.showConfirmTransferSite = false;
1380 i.setState(i.state);
1383 handleTransferSite(i: PostListing) {
1384 let form: TransferSiteForm = {
1385 user_id: i.props.post.creator_id,
1387 WebSocketService.Instance.transferSite(form);
1388 i.state.showConfirmTransferSite = false;
1389 i.setState(i.state);
1392 handleImageExpandClick(i: PostListing) {
1393 i.state.imageExpanded = !i.state.imageExpanded;
1394 i.setState(i.state);
1397 handleViewSource(i: PostListing) {
1398 i.state.viewSource = !i.state.viewSource;
1399 i.setState(i.state);
1402 handleShowAdvanced(i: PostListing) {
1403 i.state.showAdvanced = !i.state.showAdvanced;
1404 i.setState(i.state);
1408 get pointsTippy(): string {
1409 let points = i18n.t('number_of_points', {
1410 count: this.state.score,
1413 let upvotes = i18n.t('number_of_upvotes', {
1414 count: this.state.upvotes,
1417 let downvotes = i18n.t('number_of_downvotes', {
1418 count: this.state.downvotes,
1421 return `${points} • ${upvotes} • ${downvotes}`;