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';
35 import { i18n } from '../i18next';
37 interface PostListingState {
39 showRemoveDialog: boolean;
41 showBanDialog: boolean;
45 showConfirmTransferSite: boolean;
46 showConfirmTransferCommunity: boolean;
47 imageExpanded: boolean;
49 showAdvanced: boolean;
56 interface PostListingProps {
58 showCommunity?: boolean;
60 moderators?: Array<CommunityUser>;
61 admins?: Array<UserView>;
64 export class PostListing extends Component<PostListingProps, PostListingState> {
65 private emptyState: PostListingState = {
67 showRemoveDialog: false,
72 banType: BanType.Community,
73 showConfirmTransferSite: false,
74 showConfirmTransferCommunity: false,
78 my_vote: this.props.post.my_vote,
79 score: this.props.post.score,
80 upvotes: this.props.post.upvotes,
81 downvotes: this.props.post.downvotes,
84 constructor(props: any, context: any) {
85 super(props, context);
87 this.state = this.emptyState;
88 this.handlePostLike = this.handlePostLike.bind(this);
89 this.handlePostDisLike = this.handlePostDisLike.bind(this);
90 this.handleEditPost = this.handleEditPost.bind(this);
91 this.handleEditCancel = this.handleEditCancel.bind(this);
94 componentWillReceiveProps(nextProps: PostListingProps) {
95 this.state.my_vote = nextProps.post.my_vote;
96 this.state.upvotes = nextProps.post.upvotes;
97 this.state.downvotes = nextProps.post.downvotes;
98 this.state.score = nextProps.post.score;
99 this.setState(this.state);
105 {!this.state.showEdit ? (
113 post={this.props.post}
114 onEdit={this.handleEditPost}
115 onCancel={this.handleEditCancel}
127 {this.props.post.url &&
128 this.props.showBody &&
129 this.props.post.embed_title && (
130 <IFramelyCard post={this.props.post} />
132 {this.props.showBody && this.props.post.body && (
134 {this.state.viewSource ? (
135 <pre>{this.props.post.body}</pre>
139 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
149 imgThumb(src: string) {
150 let post = this.props.post;
153 className={`img-fluid thumbnail rounded ${
154 (post.nsfw || post.community_nsfw) && 'img-blur'
161 getImage(thumbnail: boolean = false) {
162 let post = this.props.post;
163 if (isImage(post.url)) {
164 if (post.url.includes('pictshare')) {
165 return pictshareImage(post.url, thumbnail);
166 } else if (post.thumbnail_url) {
167 return pictshareImage(post.thumbnail_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')}
318 !(new URL(post.url).hostname == window.location.hostname) && (
319 <small class="d-inline-block">
321 className="ml-2 text-muted font-italic"
326 {new URL(post.url).hostname}
327 <svg class="ml-1 icon icon-inline">
328 <use xlinkHref="#icon-external-link"></use>
333 {(isImage(post.url) || this.props.post.thumbnail_url) && (
335 {!this.state.imageExpanded ? (
337 class="text-monospace unselectable pointer ml-2 text-muted small"
338 data-tippy-content={i18n.t('expand_here')}
339 onClick={linkEvent(this, this.handleImageExpandClick)}
341 <svg class="icon icon-inline">
342 <use xlinkHref="#icon-plus-square"></use>
348 class="text-monospace unselectable pointer ml-2 text-muted small"
349 onClick={linkEvent(this, this.handleImageExpandClick)}
351 <svg class="icon icon-inline">
352 <use xlinkHref="#icon-minus-square"></use>
360 this.handleImageExpandClick
364 class="img-fluid img-expanded"
365 src={this.getImage()}
374 <small className="ml-2 text-muted font-italic">
380 className="unselectable pointer ml-2 text-muted font-italic"
381 data-tippy-content={i18n.t('deleted')}
383 <svg class={`icon icon-inline text-danger`}>
384 <use xlinkHref="#icon-trash"></use>
390 className="unselectable pointer ml-2 text-muted font-italic"
391 data-tippy-content={i18n.t('locked')}
393 <svg class={`icon icon-inline text-danger`}>
394 <use xlinkHref="#icon-lock"></use>
400 className="unselectable pointer ml-2 text-muted font-italic"
401 data-tippy-content={i18n.t('stickied')}
403 <svg class={`icon icon-inline text-success`}>
404 <use xlinkHref="#icon-pin"></use>
409 <small className="ml-2 text-muted font-italic">
417 <div className="details col-12">
418 <ul class="list-inline mb-0 text-muted small">
419 <li className="list-inline-item">
420 <span>{i18n.t('by')} </span>
423 name: post.creator_name,
424 avatar: post.creator_avatar,
428 <span className="mx-1 badge badge-light">
433 <span className="mx-1 badge badge-light">
437 {(post.banned_from_community || post.banned) && (
438 <span className="mx-1 badge badge-danger">
442 {this.props.showCommunity && (
444 <span> {i18n.t('to')} </span>
445 <Link to={`/c/${post.community_name}`}>
446 {post.community_name}
451 <li className="list-inline-item">•</li>
452 <li className="list-inline-item">
454 <MomentTime data={post} />
459 <li className="list-inline-item">•</li>
460 <li className="list-inline-item">
461 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
463 className="text-muted"
464 data-tippy-content={md.render(previewLines(post.body))}
465 data-tippy-allowHtml={true}
466 to={`/post/${post.id}`}
468 <svg class="mr-1 icon icon-inline">
469 <use xlinkHref="#icon-book-open"></use>
475 <li className="list-inline-item">•</li>
476 {this.state.upvotes !== this.state.score && (
479 class="unselectable pointer mr-2"
480 data-tippy-content={this.pointsTippy}
482 <li className="list-inline-item">
483 <span className="text-muted">
484 <svg class="small icon icon-inline mr-1">
485 <use xlinkHref="#icon-arrow-up"></use>
490 <li className="list-inline-item">
491 <span className="text-muted">
492 <svg class="small icon icon-inline mr-1">
493 <use xlinkHref="#icon-arrow-down"></use>
495 {this.state.downvotes}
499 <li className="list-inline-item">•</li>
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 {post.number_of_comments}
517 {this.props.post.duplicates && (
518 <ul class="list-inline mb-1 small text-muted">
520 <li className="list-inline-item mr-2">
521 {i18n.t('cross_posted_to')}
523 {this.props.post.duplicates.map(post => (
524 <li className="list-inline-item mr-2">
525 <Link to={`/post/${post.id}`}>
526 {post.community_name}
533 <ul class="list-inline mb-1 text-muted font-weight-bold">
534 {UserService.Instance.user && (
536 {this.props.showBody && (
538 <li className="list-inline-item">
540 class="btn btn-sm btn-link btn-animate text-muted"
541 onClick={linkEvent(this, this.handleSavePostClick)}
543 post.saved ? i18n.t('unsave') : i18n.t('save')
547 class={`icon icon-inline ${
548 post.saved && 'text-warning'
551 <use xlinkHref="#icon-star"></use>
555 <li className="list-inline-item">
557 class="btn btn-sm btn-link btn-animate text-muted"
558 to={`/create_post${this.crossPostParams}`}
559 title={i18n.t('cross_post')}
561 <svg class="icon icon-inline">
562 <use xlinkHref="#icon-copy"></use>
568 {this.myPost && this.props.showBody && (
570 <li className="list-inline-item">
572 class="btn btn-sm btn-link btn-animate text-muted"
573 onClick={linkEvent(this, this.handleEditClick)}
574 data-tippy-content={i18n.t('edit')}
576 <svg class="icon icon-inline">
577 <use xlinkHref="#icon-edit"></use>
581 <li className="list-inline-item">
583 class="btn btn-sm btn-link btn-animate text-muted"
584 onClick={linkEvent(this, this.handleDeleteClick)}
592 class={`icon icon-inline ${
593 post.deleted && 'text-danger'
596 <use xlinkHref="#icon-trash"></use>
603 {!this.state.showAdvanced && this.props.showBody ? (
604 <li className="list-inline-item">
606 class="btn btn-sm btn-link btn-animate text-muted"
607 onClick={linkEvent(this, this.handleShowAdvanced)}
608 data-tippy-content={i18n.t('more')}
610 <svg class="icon icon-inline">
611 <use xlinkHref="#icon-more-vertical"></use>
617 {this.props.showBody && post.body && (
618 <li className="list-inline-item">
620 class="btn btn-sm btn-link btn-animate text-muted"
621 onClick={linkEvent(this, this.handleViewSource)}
622 data-tippy-content={i18n.t('view_source')}
625 class={`icon icon-inline ${
626 this.state.viewSource && 'text-success'
629 <use xlinkHref="#icon-file-text"></use>
634 {this.canModOnSelf && (
636 <li className="list-inline-item">
638 class="btn btn-sm btn-link btn-animate text-muted"
639 onClick={linkEvent(this, this.handleModLock)}
647 class={`icon icon-inline ${
648 post.locked && 'text-danger'
651 <use xlinkHref="#icon-lock"></use>
655 <li className="list-inline-item">
657 class="btn btn-sm btn-link btn-animate text-muted"
658 onClick={linkEvent(this, this.handleModSticky)}
666 class={`icon icon-inline ${
667 post.stickied && 'text-success'
670 <use xlinkHref="#icon-pin"></use>
676 {/* Mods can ban from community, and appoint as mods to community */}
677 {(this.canMod || this.canAdmin) && (
678 <li className="list-inline-item">
684 this.handleModRemoveShow
694 this.handleModRemoveSubmit
705 <li className="list-inline-item">
706 {!post.banned_from_community ? (
711 this.handleModBanFromCommunityShow
721 this.handleModBanFromCommunitySubmit
729 {!post.banned_from_community && (
730 <li className="list-inline-item">
735 this.handleAddModToCommunity
739 ? i18n.t('remove_as_mod')
740 : i18n.t('appoint_as_mod')}
746 {/* Community creators and admins can transfer community to another mod */}
747 {(this.amCommunityCreator || this.canAdmin) &&
749 <li className="list-inline-item">
750 {!this.state.showConfirmTransferCommunity ? (
755 this.handleShowConfirmTransferCommunity
758 {i18n.t('transfer_community')}
762 <span class="d-inline-block mr-1">
763 {i18n.t('are_you_sure')}
766 class="pointer d-inline-block mr-1"
769 this.handleTransferCommunity
775 class="pointer d-inline-block"
779 .handleCancelShowConfirmTransferCommunity
788 {/* Admins can ban from all, and appoint other admins */}
792 <li className="list-inline-item">
798 this.handleModBanShow
801 {i18n.t('ban_from_site')}
808 this.handleModBanSubmit
811 {i18n.t('unban_from_site')}
817 <li className="list-inline-item">
820 onClick={linkEvent(this, this.handleAddAdmin)}
823 ? i18n.t('remove_as_admin')
824 : i18n.t('appoint_as_admin')}
830 {/* Site Creator can transfer to another admin */}
831 {this.amSiteCreator && this.isAdmin && (
832 <li className="list-inline-item">
833 {!this.state.showConfirmTransferSite ? (
838 this.handleShowConfirmTransferSite
841 {i18n.t('transfer_site')}
845 <span class="d-inline-block mr-1">
846 {i18n.t('are_you_sure')}
849 class="pointer d-inline-block mr-1"
852 this.handleTransferSite
858 class="pointer d-inline-block"
861 this.handleCancelShowConfirmTransferSite
875 {this.state.showRemoveDialog && (
878 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
882 class="form-control mr-2"
883 placeholder={i18n.t('reason')}
884 value={this.state.removeReason}
885 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
887 <button type="submit" class="btn btn-secondary">
888 {i18n.t('remove_post')}
892 {this.state.showBanDialog && (
893 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
894 <div class="form-group row">
895 <label class="col-form-label" htmlFor="post-listing-reason">
900 id="post-listing-reason"
901 class="form-control mr-2"
902 placeholder={i18n.t('reason')}
903 value={this.state.banReason}
904 onInput={linkEvent(this, this.handleModBanReasonChange)}
907 {/* TODO hold off on expires until later */}
908 {/* <div class="form-group row"> */}
909 {/* <label class="col-form-label">Expires</label> */}
910 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
912 <div class="form-group row">
913 <button type="submit" class="btn btn-secondary">
914 {i18n.t('ban')} {post.creator_name}
926 private get myPost(): boolean {
928 UserService.Instance.user &&
929 this.props.post.creator_id == UserService.Instance.user.id
933 get isMod(): boolean {
935 this.props.moderators &&
937 this.props.moderators.map(m => m.user_id),
938 this.props.post.creator_id
943 get isAdmin(): boolean {
947 this.props.admins.map(a => a.id),
948 this.props.post.creator_id
953 get canMod(): boolean {
954 if (this.props.admins && this.props.moderators) {
955 let adminsThenMods = this.props.admins
957 .concat(this.props.moderators.map(m => m.user_id));
960 UserService.Instance.user,
962 this.props.post.creator_id
969 get canModOnSelf(): boolean {
970 if (this.props.admins && this.props.moderators) {
971 let adminsThenMods = this.props.admins
973 .concat(this.props.moderators.map(m => m.user_id));
976 UserService.Instance.user,
978 this.props.post.creator_id,
986 get canAdmin(): boolean {
990 UserService.Instance.user,
991 this.props.admins.map(a => a.id),
992 this.props.post.creator_id
997 get amCommunityCreator(): boolean {
999 this.props.moderators &&
1000 UserService.Instance.user &&
1001 this.props.post.creator_id != UserService.Instance.user.id &&
1002 UserService.Instance.user.id == this.props.moderators[0].user_id
1006 get amSiteCreator(): boolean {
1008 this.props.admins &&
1009 UserService.Instance.user &&
1010 this.props.post.creator_id != UserService.Instance.user.id &&
1011 UserService.Instance.user.id == this.props.admins[0].id
1015 handlePostLike(i: PostListing) {
1016 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1018 if (i.state.my_vote == 1) {
1021 } else if (i.state.my_vote == -1) {
1022 i.state.downvotes--;
1030 i.state.my_vote = new_vote;
1032 let form: CreatePostLikeForm = {
1033 post_id: i.props.post.id,
1034 score: i.state.my_vote,
1037 WebSocketService.Instance.likePost(form);
1038 i.setState(i.state);
1042 handlePostDisLike(i: PostListing) {
1043 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1045 if (i.state.my_vote == 1) {
1048 i.state.downvotes++;
1049 } else if (i.state.my_vote == -1) {
1050 i.state.downvotes--;
1053 i.state.downvotes++;
1057 i.state.my_vote = new_vote;
1059 let form: CreatePostLikeForm = {
1060 post_id: i.props.post.id,
1061 score: i.state.my_vote,
1064 WebSocketService.Instance.likePost(form);
1065 i.setState(i.state);
1069 handleEditClick(i: PostListing) {
1070 i.state.showEdit = true;
1071 i.setState(i.state);
1074 handleEditCancel() {
1075 this.state.showEdit = false;
1076 this.setState(this.state);
1079 // The actual editing is done in the recieve for post
1081 this.state.showEdit = false;
1082 this.setState(this.state);
1085 handleDeleteClick(i: PostListing) {
1086 let deleteForm: PostFormI = {
1087 body: i.props.post.body,
1088 community_id: i.props.post.community_id,
1089 name: i.props.post.name,
1090 url: i.props.post.url,
1091 edit_id: i.props.post.id,
1092 creator_id: i.props.post.creator_id,
1093 deleted: !i.props.post.deleted,
1094 nsfw: i.props.post.nsfw,
1097 WebSocketService.Instance.editPost(deleteForm);
1100 handleSavePostClick(i: PostListing) {
1101 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1102 let form: SavePostForm = {
1103 post_id: i.props.post.id,
1107 WebSocketService.Instance.savePost(form);
1110 get crossPostParams(): string {
1111 let params = `?title=${this.props.post.name}`;
1112 let post = this.props.post;
1115 params += `&url=${post.url}`;
1117 if (this.props.post.body) {
1118 params += `&body=${this.props.post.body}`;
1123 handleModRemoveShow(i: PostListing) {
1124 i.state.showRemoveDialog = true;
1125 i.setState(i.state);
1128 handleModRemoveReasonChange(i: PostListing, event: any) {
1129 i.state.removeReason = event.target.value;
1130 i.setState(i.state);
1133 handleModRemoveSubmit(i: PostListing) {
1134 event.preventDefault();
1135 let form: PostFormI = {
1136 name: i.props.post.name,
1137 community_id: i.props.post.community_id,
1138 edit_id: i.props.post.id,
1139 creator_id: i.props.post.creator_id,
1140 removed: !i.props.post.removed,
1141 reason: i.state.removeReason,
1142 nsfw: i.props.post.nsfw,
1145 WebSocketService.Instance.editPost(form);
1147 i.state.showRemoveDialog = false;
1148 i.setState(i.state);
1151 handleModLock(i: PostListing) {
1152 let form: PostFormI = {
1153 name: i.props.post.name,
1154 community_id: i.props.post.community_id,
1155 edit_id: i.props.post.id,
1156 creator_id: i.props.post.creator_id,
1157 nsfw: i.props.post.nsfw,
1158 locked: !i.props.post.locked,
1161 WebSocketService.Instance.editPost(form);
1164 handleModSticky(i: PostListing) {
1165 let form: PostFormI = {
1166 name: i.props.post.name,
1167 community_id: i.props.post.community_id,
1168 edit_id: i.props.post.id,
1169 creator_id: i.props.post.creator_id,
1170 nsfw: i.props.post.nsfw,
1171 stickied: !i.props.post.stickied,
1174 WebSocketService.Instance.editPost(form);
1177 handleModBanFromCommunityShow(i: PostListing) {
1178 i.state.showBanDialog = true;
1179 i.state.banType = BanType.Community;
1180 i.setState(i.state);
1183 handleModBanShow(i: PostListing) {
1184 i.state.showBanDialog = true;
1185 i.state.banType = BanType.Site;
1186 i.setState(i.state);
1189 handleModBanReasonChange(i: PostListing, event: any) {
1190 i.state.banReason = event.target.value;
1191 i.setState(i.state);
1194 handleModBanExpiresChange(i: PostListing, event: any) {
1195 i.state.banExpires = event.target.value;
1196 i.setState(i.state);
1199 handleModBanFromCommunitySubmit(i: PostListing) {
1200 i.state.banType = BanType.Community;
1201 i.setState(i.state);
1202 i.handleModBanBothSubmit(i);
1205 handleModBanSubmit(i: PostListing) {
1206 i.state.banType = BanType.Site;
1207 i.setState(i.state);
1208 i.handleModBanBothSubmit(i);
1211 handleModBanBothSubmit(i: PostListing) {
1212 event.preventDefault();
1214 if (i.state.banType == BanType.Community) {
1215 let form: BanFromCommunityForm = {
1216 user_id: i.props.post.creator_id,
1217 community_id: i.props.post.community_id,
1218 ban: !i.props.post.banned_from_community,
1219 reason: i.state.banReason,
1220 expires: getUnixTime(i.state.banExpires),
1222 WebSocketService.Instance.banFromCommunity(form);
1224 let form: BanUserForm = {
1225 user_id: i.props.post.creator_id,
1226 ban: !i.props.post.banned,
1227 reason: i.state.banReason,
1228 expires: getUnixTime(i.state.banExpires),
1230 WebSocketService.Instance.banUser(form);
1233 i.state.showBanDialog = false;
1234 i.setState(i.state);
1237 handleAddModToCommunity(i: PostListing) {
1238 let form: AddModToCommunityForm = {
1239 user_id: i.props.post.creator_id,
1240 community_id: i.props.post.community_id,
1243 WebSocketService.Instance.addModToCommunity(form);
1244 i.setState(i.state);
1247 handleAddAdmin(i: PostListing) {
1248 let form: AddAdminForm = {
1249 user_id: i.props.post.creator_id,
1252 WebSocketService.Instance.addAdmin(form);
1253 i.setState(i.state);
1256 handleShowConfirmTransferCommunity(i: PostListing) {
1257 i.state.showConfirmTransferCommunity = true;
1258 i.setState(i.state);
1261 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1262 i.state.showConfirmTransferCommunity = false;
1263 i.setState(i.state);
1266 handleTransferCommunity(i: PostListing) {
1267 let form: TransferCommunityForm = {
1268 community_id: i.props.post.community_id,
1269 user_id: i.props.post.creator_id,
1271 WebSocketService.Instance.transferCommunity(form);
1272 i.state.showConfirmTransferCommunity = false;
1273 i.setState(i.state);
1276 handleShowConfirmTransferSite(i: PostListing) {
1277 i.state.showConfirmTransferSite = true;
1278 i.setState(i.state);
1281 handleCancelShowConfirmTransferSite(i: PostListing) {
1282 i.state.showConfirmTransferSite = false;
1283 i.setState(i.state);
1286 handleTransferSite(i: PostListing) {
1287 let form: TransferSiteForm = {
1288 user_id: i.props.post.creator_id,
1290 WebSocketService.Instance.transferSite(form);
1291 i.state.showConfirmTransferSite = false;
1292 i.setState(i.state);
1295 handleImageExpandClick(i: PostListing) {
1296 i.state.imageExpanded = !i.state.imageExpanded;
1297 i.setState(i.state);
1300 handleViewSource(i: PostListing) {
1301 i.state.viewSource = !i.state.viewSource;
1302 i.setState(i.state);
1305 handleShowAdvanced(i: PostListing) {
1306 i.state.showAdvanced = !i.state.showAdvanced;
1307 i.setState(i.state);
1311 get pointsTippy(): string {
1312 let points = i18n.t('number_of_points', {
1313 count: this.state.score,
1316 let upvotes = i18n.t('number_of_upvotes', {
1317 count: this.state.upvotes,
1320 let downvotes = i18n.t('number_of_downvotes', {
1321 count: this.state.downvotes,
1324 return `${points} • ${upvotes} • ${downvotes}`;