1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { WebSocketService, UserService } from '../services';
14 AddModToCommunityForm,
17 TransferCommunityForm,
18 } from '../interfaces';
19 import { MomentTime } from './moment-time';
20 import { PostForm } from './post-form';
21 import { IFramelyCard } from './iframely-card';
22 import { UserListing } from './user-listing';
23 import { CommunityLink } from './community-link';
37 import { i18n } from '../i18next';
39 interface PostListingState {
41 showRemoveDialog: boolean;
43 showBanDialog: boolean;
47 showConfirmTransferSite: boolean;
48 showConfirmTransferCommunity: boolean;
49 imageExpanded: boolean;
51 showAdvanced: boolean;
58 interface PostListingProps {
60 showCommunity?: boolean;
62 moderators?: Array<CommunityUser>;
63 admins?: Array<UserView>;
66 export class PostListing extends Component<PostListingProps, PostListingState> {
67 private emptyState: PostListingState = {
69 showRemoveDialog: false,
74 banType: BanType.Community,
75 showConfirmTransferSite: false,
76 showConfirmTransferCommunity: false,
80 my_vote: this.props.post.my_vote,
81 score: this.props.post.score,
82 upvotes: this.props.post.upvotes,
83 downvotes: this.props.post.downvotes,
86 constructor(props: any, context: any) {
87 super(props, context);
89 this.state = this.emptyState;
90 this.handlePostLike = this.handlePostLike.bind(this);
91 this.handlePostDisLike = this.handlePostDisLike.bind(this);
92 this.handleEditPost = this.handleEditPost.bind(this);
93 this.handleEditCancel = this.handleEditCancel.bind(this);
96 componentWillReceiveProps(nextProps: PostListingProps) {
97 this.state.my_vote = nextProps.post.my_vote;
98 this.state.upvotes = nextProps.post.upvotes;
99 this.state.downvotes = nextProps.post.downvotes;
100 this.state.score = nextProps.post.score;
101 this.setState(this.state);
107 {!this.state.showEdit ? (
115 post={this.props.post}
116 onEdit={this.handleEditPost}
117 onCancel={this.handleEditCancel}
129 {this.props.post.url &&
130 this.props.showBody &&
131 this.props.post.embed_title && (
132 <IFramelyCard post={this.props.post} />
134 {this.props.showBody && this.props.post.body && (
136 {this.state.viewSource ? (
137 <pre>{this.props.post.body}</pre>
141 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
151 imgThumb(src: string) {
152 let post = this.props.post;
155 className={`img-fluid thumbnail rounded ${
156 (post.nsfw || post.community_nsfw) && 'img-blur'
163 getImage(thumbnail: boolean = false) {
164 let post = this.props.post;
165 if (isImage(post.url)) {
166 if (post.url.includes('pictshare')) {
167 return pictshareImage(post.url, thumbnail);
171 } else if (post.thumbnail_url) {
172 return pictshareImage(post.thumbnail_url, thumbnail);
177 let post = this.props.post;
179 if (isImage(post.url)) {
182 class="text-body pointer"
183 data-tippy-content={i18n.t('expand_here')}
184 onClick={linkEvent(this, this.handleImageExpandClick)}
186 {this.imgThumb(this.getImage(true))}
187 <svg class="icon mini-overlay">
188 <use xlinkHref="#icon-image"></use>
192 } else if (post.thumbnail_url) {
195 className="text-body"
200 {this.imgThumb(this.getImage(true))}
201 <svg class="icon mini-overlay">
202 <use xlinkHref="#icon-external-link"></use>
206 } else if (post.url) {
207 if (isVideo(post.url)) {
209 <div class="embed-responsive embed-responsive-16by9">
215 class="embed-responsive-item"
217 <source src={post.url} type="video/mp4" />
224 className="text-body"
229 <svg class="icon thumbnail">
230 <use xlinkHref="#icon-external-link"></use>
238 className="text-body"
239 to={`/post/${post.id}`}
240 title={i18n.t('comments')}
242 <svg class="icon thumbnail">
243 <use xlinkHref="#icon-message-square"></use>
251 let post = this.props.post;
254 <div className={`vote-bar col-1 pr-0 small text-center`}>
256 className={`btn-animate btn btn-link p-0 ${
257 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
259 onClick={linkEvent(this, this.handlePostLike)}
260 data-tippy-content={i18n.t('upvote')}
262 <svg class="icon upvote">
263 <use xlinkHref="#icon-arrow-up1"></use>
267 class={`unselectable pointer font-weight-bold text-muted px-1`}
268 data-tippy-content={this.pointsTippy}
272 {WebSocketService.Instance.site.enable_downvotes && (
274 className={`btn-animate btn btn-link p-0 ${
275 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
277 onClick={linkEvent(this, this.handlePostDisLike)}
278 data-tippy-content={i18n.t('downvote')}
280 <svg class="icon downvote">
281 <use xlinkHref="#icon-arrow-down1"></use>
286 {!this.state.imageExpanded && (
287 <div class="col-3 col-sm-2 pr-0 mt-1">
288 <div class="position-relative">{this.thumbnail()}</div>
292 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
295 <div className="col-12">
296 <div className="post-title">
297 <h5 className="mb-0 d-inline">
298 {this.props.showBody && post.url ? (
300 className="text-body"
309 className="text-body"
310 to={`/post/${post.id}`}
311 title={i18n.t('comments')}
317 {post.url && !(hostname(post.url) == window.location.hostname) && (
318 <small class="d-inline-block">
320 className="ml-2 text-muted font-italic"
326 <svg class="ml-1 icon icon-inline">
327 <use xlinkHref="#icon-external-link"></use>
332 {(isImage(post.url) || this.props.post.thumbnail_url) && (
334 {!this.state.imageExpanded ? (
336 class="text-monospace unselectable pointer ml-2 text-muted small"
337 data-tippy-content={i18n.t('expand_here')}
338 onClick={linkEvent(this, this.handleImageExpandClick)}
340 <svg class="icon icon-inline">
341 <use xlinkHref="#icon-plus-square"></use>
347 class="text-monospace unselectable pointer ml-2 text-muted small"
348 onClick={linkEvent(this, this.handleImageExpandClick)}
350 <svg class="icon icon-inline">
351 <use xlinkHref="#icon-minus-square"></use>
359 this.handleImageExpandClick
363 class="img-fluid img-expanded"
364 src={this.getImage()}
373 <small className="ml-2 text-muted font-italic">
379 className="unselectable pointer ml-2 text-muted font-italic"
380 data-tippy-content={i18n.t('deleted')}
382 <svg class={`icon icon-inline text-danger`}>
383 <use xlinkHref="#icon-trash"></use>
389 className="unselectable pointer ml-2 text-muted font-italic"
390 data-tippy-content={i18n.t('locked')}
392 <svg class={`icon icon-inline text-danger`}>
393 <use xlinkHref="#icon-lock"></use>
399 className="unselectable pointer ml-2 text-muted font-italic"
400 data-tippy-content={i18n.t('stickied')}
402 <svg class={`icon icon-inline text-success`}>
403 <use xlinkHref="#icon-pin"></use>
408 <small className="ml-2 text-muted font-italic">
416 <div className="details col-12">
417 <ul class="list-inline mb-0 text-muted small">
418 <li className="list-inline-item">
419 <span>{i18n.t('by')} </span>
422 name: post.creator_name,
423 avatar: post.creator_avatar,
425 local: post.creator_local,
426 actor_id: post.creator_actor_id,
430 <span className="mx-1 badge badge-light">
435 <span className="mx-1 badge badge-light">
439 {(post.banned_from_community || post.banned) && (
440 <span className="mx-1 badge badge-danger">
444 {this.props.showCommunity && (
446 <span> {i18n.t('to')} </span>
449 name: post.community_name,
450 id: post.community_id,
451 local: post.community_local,
452 actor_id: post.community_actor_id,
458 <li className="list-inline-item">•</li>
459 <li className="list-inline-item">
461 <MomentTime data={post} />
466 <li className="list-inline-item">•</li>
467 <li className="list-inline-item">
468 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
470 className="text-muted"
471 data-tippy-content={md.render(previewLines(post.body))}
472 data-tippy-allowHtml={true}
473 to={`/post/${post.id}`}
475 <svg class="mr-1 icon icon-inline">
476 <use xlinkHref="#icon-book-open"></use>
482 <li className="list-inline-item">•</li>
483 {this.state.upvotes !== this.state.score && (
486 class="unselectable pointer mr-2"
487 data-tippy-content={this.pointsTippy}
489 <li className="list-inline-item">
490 <span className="text-muted">
491 <svg class="small icon icon-inline mr-1">
492 <use xlinkHref="#icon-arrow-up"></use>
497 <li className="list-inline-item">
498 <span className="text-muted">
499 <svg class="small icon icon-inline mr-1">
500 <use xlinkHref="#icon-arrow-down"></use>
502 {this.state.downvotes}
506 <li className="list-inline-item">•</li>
509 <li className="list-inline-item">
511 className="text-muted"
512 title={i18n.t('number_of_comments', {
513 count: post.number_of_comments,
515 to={`/post/${post.id}`}
517 <svg class="mr-1 icon icon-inline">
518 <use xlinkHref="#icon-message-square"></use>
520 {post.number_of_comments}
524 {this.props.post.duplicates && (
525 <ul class="list-inline mb-1 small text-muted">
527 <li className="list-inline-item mr-2">
528 {i18n.t('cross_posted_to')}
530 {this.props.post.duplicates.map(post => (
531 <li className="list-inline-item mr-2">
532 <Link to={`/post/${post.id}`}>
533 {post.community_name}
540 <ul class="list-inline mb-1 text-muted font-weight-bold">
541 {UserService.Instance.user && (
543 {this.props.showBody && (
545 <li className="list-inline-item">
547 class="btn btn-sm btn-link btn-animate text-muted"
548 onClick={linkEvent(this, this.handleSavePostClick)}
550 post.saved ? i18n.t('unsave') : i18n.t('save')
554 class={`icon icon-inline ${
555 post.saved && 'text-warning'
558 <use xlinkHref="#icon-star"></use>
562 <li className="list-inline-item">
564 class="btn btn-sm btn-link btn-animate text-muted"
565 to={`/create_post${this.crossPostParams}`}
566 title={i18n.t('cross_post')}
568 <svg class="icon icon-inline">
569 <use xlinkHref="#icon-copy"></use>
575 {this.myPost && this.props.showBody && (
577 <li className="list-inline-item">
579 class="btn btn-sm btn-link btn-animate text-muted"
580 onClick={linkEvent(this, this.handleEditClick)}
581 data-tippy-content={i18n.t('edit')}
583 <svg class="icon icon-inline">
584 <use xlinkHref="#icon-edit"></use>
588 <li className="list-inline-item">
590 class="btn btn-sm btn-link btn-animate text-muted"
591 onClick={linkEvent(this, this.handleDeleteClick)}
599 class={`icon icon-inline ${
600 post.deleted && 'text-danger'
603 <use xlinkHref="#icon-trash"></use>
610 {!this.state.showAdvanced && this.props.showBody ? (
611 <li className="list-inline-item">
613 class="btn btn-sm btn-link btn-animate text-muted"
614 onClick={linkEvent(this, this.handleShowAdvanced)}
615 data-tippy-content={i18n.t('more')}
617 <svg class="icon icon-inline">
618 <use xlinkHref="#icon-more-vertical"></use>
624 {this.props.showBody && post.body && (
625 <li className="list-inline-item">
627 class="btn btn-sm btn-link btn-animate text-muted"
628 onClick={linkEvent(this, this.handleViewSource)}
629 data-tippy-content={i18n.t('view_source')}
632 class={`icon icon-inline ${
633 this.state.viewSource && 'text-success'
636 <use xlinkHref="#icon-file-text"></use>
641 {this.canModOnSelf && (
643 <li className="list-inline-item">
645 class="btn btn-sm btn-link btn-animate text-muted"
646 onClick={linkEvent(this, this.handleModLock)}
654 class={`icon icon-inline ${
655 post.locked && 'text-danger'
658 <use xlinkHref="#icon-lock"></use>
662 <li className="list-inline-item">
664 class="btn btn-sm btn-link btn-animate text-muted"
665 onClick={linkEvent(this, this.handleModSticky)}
673 class={`icon icon-inline ${
674 post.stickied && 'text-success'
677 <use xlinkHref="#icon-pin"></use>
683 {/* Mods can ban from community, and appoint as mods to community */}
684 {(this.canMod || this.canAdmin) && (
685 <li className="list-inline-item">
691 this.handleModRemoveShow
701 this.handleModRemoveSubmit
712 <li className="list-inline-item">
713 {!post.banned_from_community ? (
718 this.handleModBanFromCommunityShow
728 this.handleModBanFromCommunitySubmit
736 {!post.banned_from_community && (
737 <li className="list-inline-item">
742 this.handleAddModToCommunity
746 ? i18n.t('remove_as_mod')
747 : i18n.t('appoint_as_mod')}
753 {/* Community creators and admins can transfer community to another mod */}
754 {(this.amCommunityCreator || this.canAdmin) &&
756 <li className="list-inline-item">
757 {!this.state.showConfirmTransferCommunity ? (
762 this.handleShowConfirmTransferCommunity
765 {i18n.t('transfer_community')}
769 <span class="d-inline-block mr-1">
770 {i18n.t('are_you_sure')}
773 class="pointer d-inline-block mr-1"
776 this.handleTransferCommunity
782 class="pointer d-inline-block"
786 .handleCancelShowConfirmTransferCommunity
795 {/* Admins can ban from all, and appoint other admins */}
799 <li className="list-inline-item">
805 this.handleModBanShow
808 {i18n.t('ban_from_site')}
815 this.handleModBanSubmit
818 {i18n.t('unban_from_site')}
824 <li className="list-inline-item">
827 onClick={linkEvent(this, this.handleAddAdmin)}
830 ? i18n.t('remove_as_admin')
831 : i18n.t('appoint_as_admin')}
837 {/* Site Creator can transfer to another admin */}
838 {this.amSiteCreator && this.isAdmin && (
839 <li className="list-inline-item">
840 {!this.state.showConfirmTransferSite ? (
845 this.handleShowConfirmTransferSite
848 {i18n.t('transfer_site')}
852 <span class="d-inline-block mr-1">
853 {i18n.t('are_you_sure')}
856 class="pointer d-inline-block mr-1"
859 this.handleTransferSite
865 class="pointer d-inline-block"
868 this.handleCancelShowConfirmTransferSite
882 {this.state.showRemoveDialog && (
885 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
889 class="form-control mr-2"
890 placeholder={i18n.t('reason')}
891 value={this.state.removeReason}
892 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
894 <button type="submit" class="btn btn-secondary">
895 {i18n.t('remove_post')}
899 {this.state.showBanDialog && (
900 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
901 <div class="form-group row">
902 <label class="col-form-label" htmlFor="post-listing-reason">
907 id="post-listing-reason"
908 class="form-control mr-2"
909 placeholder={i18n.t('reason')}
910 value={this.state.banReason}
911 onInput={linkEvent(this, this.handleModBanReasonChange)}
914 {/* TODO hold off on expires until later */}
915 {/* <div class="form-group row"> */}
916 {/* <label class="col-form-label">Expires</label> */}
917 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
919 <div class="form-group row">
920 <button type="submit" class="btn btn-secondary">
921 {i18n.t('ban')} {post.creator_name}
933 private get myPost(): boolean {
935 UserService.Instance.user &&
936 this.props.post.creator_id == UserService.Instance.user.id
940 get isMod(): boolean {
942 this.props.moderators &&
944 this.props.moderators.map(m => m.user_id),
945 this.props.post.creator_id
950 get isAdmin(): boolean {
954 this.props.admins.map(a => a.id),
955 this.props.post.creator_id
960 get canMod(): boolean {
961 if (this.props.admins && this.props.moderators) {
962 let adminsThenMods = this.props.admins
964 .concat(this.props.moderators.map(m => m.user_id));
967 UserService.Instance.user,
969 this.props.post.creator_id
976 get canModOnSelf(): boolean {
977 if (this.props.admins && this.props.moderators) {
978 let adminsThenMods = this.props.admins
980 .concat(this.props.moderators.map(m => m.user_id));
983 UserService.Instance.user,
985 this.props.post.creator_id,
993 get canAdmin(): boolean {
997 UserService.Instance.user,
998 this.props.admins.map(a => a.id),
999 this.props.post.creator_id
1004 get amCommunityCreator(): boolean {
1006 this.props.moderators &&
1007 UserService.Instance.user &&
1008 this.props.post.creator_id != UserService.Instance.user.id &&
1009 UserService.Instance.user.id == this.props.moderators[0].user_id
1013 get amSiteCreator(): boolean {
1015 this.props.admins &&
1016 UserService.Instance.user &&
1017 this.props.post.creator_id != UserService.Instance.user.id &&
1018 UserService.Instance.user.id == this.props.admins[0].id
1022 handlePostLike(i: PostListing) {
1023 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1025 if (i.state.my_vote == 1) {
1028 } else if (i.state.my_vote == -1) {
1029 i.state.downvotes--;
1037 i.state.my_vote = new_vote;
1039 let form: CreatePostLikeForm = {
1040 post_id: i.props.post.id,
1041 score: i.state.my_vote,
1044 WebSocketService.Instance.likePost(form);
1045 i.setState(i.state);
1049 handlePostDisLike(i: PostListing) {
1050 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1052 if (i.state.my_vote == 1) {
1055 i.state.downvotes++;
1056 } else if (i.state.my_vote == -1) {
1057 i.state.downvotes--;
1060 i.state.downvotes++;
1064 i.state.my_vote = new_vote;
1066 let form: CreatePostLikeForm = {
1067 post_id: i.props.post.id,
1068 score: i.state.my_vote,
1071 WebSocketService.Instance.likePost(form);
1072 i.setState(i.state);
1076 handleEditClick(i: PostListing) {
1077 i.state.showEdit = true;
1078 i.setState(i.state);
1081 handleEditCancel() {
1082 this.state.showEdit = false;
1083 this.setState(this.state);
1086 // The actual editing is done in the recieve for post
1088 this.state.showEdit = false;
1089 this.setState(this.state);
1092 handleDeleteClick(i: PostListing) {
1093 let deleteForm: PostFormI = {
1094 body: i.props.post.body,
1095 community_id: i.props.post.community_id,
1096 name: i.props.post.name,
1097 url: i.props.post.url,
1098 edit_id: i.props.post.id,
1099 creator_id: i.props.post.creator_id,
1100 deleted: !i.props.post.deleted,
1101 nsfw: i.props.post.nsfw,
1104 WebSocketService.Instance.editPost(deleteForm);
1107 handleSavePostClick(i: PostListing) {
1108 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1109 let form: SavePostForm = {
1110 post_id: i.props.post.id,
1114 WebSocketService.Instance.savePost(form);
1117 get crossPostParams(): string {
1118 let params = `?title=${this.props.post.name}`;
1119 let post = this.props.post;
1122 params += `&url=${post.url}`;
1124 if (this.props.post.body) {
1125 params += `&body=${this.props.post.body}`;
1130 handleModRemoveShow(i: PostListing) {
1131 i.state.showRemoveDialog = true;
1132 i.setState(i.state);
1135 handleModRemoveReasonChange(i: PostListing, event: any) {
1136 i.state.removeReason = event.target.value;
1137 i.setState(i.state);
1140 handleModRemoveSubmit(i: PostListing) {
1141 event.preventDefault();
1142 let form: PostFormI = {
1143 name: i.props.post.name,
1144 community_id: i.props.post.community_id,
1145 edit_id: i.props.post.id,
1146 creator_id: i.props.post.creator_id,
1147 removed: !i.props.post.removed,
1148 reason: i.state.removeReason,
1149 nsfw: i.props.post.nsfw,
1152 WebSocketService.Instance.editPost(form);
1154 i.state.showRemoveDialog = false;
1155 i.setState(i.state);
1158 handleModLock(i: PostListing) {
1159 let form: PostFormI = {
1160 name: i.props.post.name,
1161 community_id: i.props.post.community_id,
1162 edit_id: i.props.post.id,
1163 creator_id: i.props.post.creator_id,
1164 nsfw: i.props.post.nsfw,
1165 locked: !i.props.post.locked,
1168 WebSocketService.Instance.editPost(form);
1171 handleModSticky(i: PostListing) {
1172 let form: PostFormI = {
1173 name: i.props.post.name,
1174 community_id: i.props.post.community_id,
1175 edit_id: i.props.post.id,
1176 creator_id: i.props.post.creator_id,
1177 nsfw: i.props.post.nsfw,
1178 stickied: !i.props.post.stickied,
1181 WebSocketService.Instance.editPost(form);
1184 handleModBanFromCommunityShow(i: PostListing) {
1185 i.state.showBanDialog = true;
1186 i.state.banType = BanType.Community;
1187 i.setState(i.state);
1190 handleModBanShow(i: PostListing) {
1191 i.state.showBanDialog = true;
1192 i.state.banType = BanType.Site;
1193 i.setState(i.state);
1196 handleModBanReasonChange(i: PostListing, event: any) {
1197 i.state.banReason = event.target.value;
1198 i.setState(i.state);
1201 handleModBanExpiresChange(i: PostListing, event: any) {
1202 i.state.banExpires = event.target.value;
1203 i.setState(i.state);
1206 handleModBanFromCommunitySubmit(i: PostListing) {
1207 i.state.banType = BanType.Community;
1208 i.setState(i.state);
1209 i.handleModBanBothSubmit(i);
1212 handleModBanSubmit(i: PostListing) {
1213 i.state.banType = BanType.Site;
1214 i.setState(i.state);
1215 i.handleModBanBothSubmit(i);
1218 handleModBanBothSubmit(i: PostListing) {
1219 event.preventDefault();
1221 if (i.state.banType == BanType.Community) {
1222 let form: BanFromCommunityForm = {
1223 user_id: i.props.post.creator_id,
1224 community_id: i.props.post.community_id,
1225 ban: !i.props.post.banned_from_community,
1226 reason: i.state.banReason,
1227 expires: getUnixTime(i.state.banExpires),
1229 WebSocketService.Instance.banFromCommunity(form);
1231 let form: BanUserForm = {
1232 user_id: i.props.post.creator_id,
1233 ban: !i.props.post.banned,
1234 reason: i.state.banReason,
1235 expires: getUnixTime(i.state.banExpires),
1237 WebSocketService.Instance.banUser(form);
1240 i.state.showBanDialog = false;
1241 i.setState(i.state);
1244 handleAddModToCommunity(i: PostListing) {
1245 let form: AddModToCommunityForm = {
1246 user_id: i.props.post.creator_id,
1247 community_id: i.props.post.community_id,
1250 WebSocketService.Instance.addModToCommunity(form);
1251 i.setState(i.state);
1254 handleAddAdmin(i: PostListing) {
1255 let form: AddAdminForm = {
1256 user_id: i.props.post.creator_id,
1259 WebSocketService.Instance.addAdmin(form);
1260 i.setState(i.state);
1263 handleShowConfirmTransferCommunity(i: PostListing) {
1264 i.state.showConfirmTransferCommunity = true;
1265 i.setState(i.state);
1268 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1269 i.state.showConfirmTransferCommunity = false;
1270 i.setState(i.state);
1273 handleTransferCommunity(i: PostListing) {
1274 let form: TransferCommunityForm = {
1275 community_id: i.props.post.community_id,
1276 user_id: i.props.post.creator_id,
1278 WebSocketService.Instance.transferCommunity(form);
1279 i.state.showConfirmTransferCommunity = false;
1280 i.setState(i.state);
1283 handleShowConfirmTransferSite(i: PostListing) {
1284 i.state.showConfirmTransferSite = true;
1285 i.setState(i.state);
1288 handleCancelShowConfirmTransferSite(i: PostListing) {
1289 i.state.showConfirmTransferSite = false;
1290 i.setState(i.state);
1293 handleTransferSite(i: PostListing) {
1294 let form: TransferSiteForm = {
1295 user_id: i.props.post.creator_id,
1297 WebSocketService.Instance.transferSite(form);
1298 i.state.showConfirmTransferSite = false;
1299 i.setState(i.state);
1302 handleImageExpandClick(i: PostListing) {
1303 i.state.imageExpanded = !i.state.imageExpanded;
1304 i.setState(i.state);
1307 handleViewSource(i: PostListing) {
1308 i.state.viewSource = !i.state.viewSource;
1309 i.setState(i.state);
1312 handleShowAdvanced(i: PostListing) {
1313 i.state.showAdvanced = !i.state.showAdvanced;
1314 i.setState(i.state);
1318 get pointsTippy(): string {
1319 let points = i18n.t('number_of_points', {
1320 count: this.state.score,
1323 let upvotes = i18n.t('number_of_upvotes', {
1324 count: this.state.upvotes,
1327 let downvotes = i18n.t('number_of_downvotes', {
1328 count: this.state.downvotes,
1331 return `${points} • ${upvotes} • ${downvotes}`;