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);
167 post.thumbnail_url &&
168 post.thumbnail_url.includes('pictshare')
170 return pictshareImage(post.thumbnail_url, thumbnail);
174 } else if (post.thumbnail_url) {
175 return pictshareImage(post.thumbnail_url, thumbnail);
180 let post = this.props.post;
182 if (isImage(post.url)) {
185 class="text-body pointer"
186 data-tippy-content={i18n.t('expand_here')}
187 onClick={linkEvent(this, this.handleImageExpandClick)}
189 {this.imgThumb(this.getImage(true))}
190 <svg class="icon mini-overlay">
191 <use xlinkHref="#icon-image"></use>
195 } else if (post.thumbnail_url) {
198 className="text-body"
203 {this.imgThumb(this.getImage(true))}
204 <svg class="icon mini-overlay">
205 <use xlinkHref="#icon-external-link"></use>
209 } else if (post.url) {
210 if (isVideo(post.url)) {
212 <div class="embed-responsive embed-responsive-16by9">
218 class="embed-responsive-item"
220 <source src={post.url} type="video/mp4" />
227 className="text-body"
232 <svg class="icon thumbnail">
233 <use xlinkHref="#icon-external-link"></use>
241 className="text-body"
242 to={`/post/${post.id}`}
243 title={i18n.t('comments')}
245 <svg class="icon thumbnail">
246 <use xlinkHref="#icon-message-square"></use>
254 let post = this.props.post;
257 <div className={`vote-bar col-1 pr-0 small text-center`}>
259 className={`btn-animate btn btn-link p-0 ${
260 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
262 onClick={linkEvent(this, this.handlePostLike)}
263 data-tippy-content={i18n.t('upvote')}
265 <svg class="icon upvote">
266 <use xlinkHref="#icon-arrow-up1"></use>
270 class={`unselectable pointer font-weight-bold text-muted px-1`}
271 data-tippy-content={this.pointsTippy}
275 {WebSocketService.Instance.site.enable_downvotes && (
277 className={`btn-animate btn btn-link p-0 ${
278 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
280 onClick={linkEvent(this, this.handlePostDisLike)}
281 data-tippy-content={i18n.t('downvote')}
283 <svg class="icon downvote">
284 <use xlinkHref="#icon-arrow-down1"></use>
289 {!this.state.imageExpanded && (
290 <div class="col-3 col-sm-2 pr-0 mt-1">
291 <div class="position-relative">{this.thumbnail()}</div>
295 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
298 <div className="col-12">
299 <div className="post-title">
300 <h5 className="mb-0 d-inline">
301 {this.props.showBody && post.url ? (
303 className="text-body"
312 className="text-body"
313 to={`/post/${post.id}`}
314 title={i18n.t('comments')}
321 !(new URL(post.url).hostname == window.location.hostname) && (
322 <small class="d-inline-block">
324 className="ml-2 text-muted font-italic"
329 {new URL(post.url).hostname}
330 <svg class="ml-1 icon icon-inline">
331 <use xlinkHref="#icon-external-link"></use>
336 {(isImage(post.url) || this.props.post.thumbnail_url) && (
338 {!this.state.imageExpanded ? (
340 class="text-monospace unselectable pointer ml-2 text-muted small"
341 data-tippy-content={i18n.t('expand_here')}
342 onClick={linkEvent(this, this.handleImageExpandClick)}
344 <svg class="icon icon-inline">
345 <use xlinkHref="#icon-plus-square"></use>
351 class="text-monospace unselectable pointer ml-2 text-muted small"
352 onClick={linkEvent(this, this.handleImageExpandClick)}
354 <svg class="icon icon-inline">
355 <use xlinkHref="#icon-minus-square"></use>
363 this.handleImageExpandClick
367 class="img-fluid img-expanded"
368 src={this.getImage()}
377 <small className="ml-2 text-muted font-italic">
383 className="unselectable pointer ml-2 text-muted font-italic"
384 data-tippy-content={i18n.t('deleted')}
386 <svg class={`icon icon-inline text-danger`}>
387 <use xlinkHref="#icon-trash"></use>
393 className="unselectable pointer ml-2 text-muted font-italic"
394 data-tippy-content={i18n.t('locked')}
396 <svg class={`icon icon-inline text-danger`}>
397 <use xlinkHref="#icon-lock"></use>
403 className="unselectable pointer ml-2 text-muted font-italic"
404 data-tippy-content={i18n.t('stickied')}
406 <svg class={`icon icon-inline text-success`}>
407 <use xlinkHref="#icon-pin"></use>
412 <small className="ml-2 text-muted font-italic">
420 <div className="details col-12">
421 <ul class="list-inline mb-0 text-muted small">
422 <li className="list-inline-item">
423 <span>{i18n.t('by')} </span>
426 name: post.creator_name,
427 avatar: post.creator_avatar,
431 <span className="mx-1 badge badge-light">
436 <span className="mx-1 badge badge-light">
440 {(post.banned_from_community || post.banned) && (
441 <span className="mx-1 badge badge-danger">
445 {this.props.showCommunity && (
447 <span> {i18n.t('to')} </span>
448 <Link to={`/c/${post.community_name}`}>
449 {post.community_name}
454 <li className="list-inline-item">•</li>
455 <li className="list-inline-item">
457 <MomentTime data={post} />
462 <li className="list-inline-item">•</li>
463 <li className="list-inline-item">
464 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
466 className="text-muted"
467 data-tippy-content={md.render(previewLines(post.body))}
468 data-tippy-allowHtml={true}
469 to={`/post/${post.id}`}
471 <svg class="mr-1 icon icon-inline">
472 <use xlinkHref="#icon-book-open"></use>
478 <li className="list-inline-item">•</li>
479 {this.state.upvotes !== this.state.score && (
482 class="unselectable pointer mr-2"
483 data-tippy-content={this.pointsTippy}
485 <li className="list-inline-item">
486 <span className="text-muted">
487 <svg class="small icon icon-inline mr-1">
488 <use xlinkHref="#icon-arrow-up"></use>
493 <li className="list-inline-item">
494 <span className="text-muted">
495 <svg class="small icon icon-inline mr-1">
496 <use xlinkHref="#icon-arrow-down"></use>
498 {this.state.downvotes}
502 <li className="list-inline-item">•</li>
505 <li className="list-inline-item">
507 className="text-muted"
508 title={i18n.t('number_of_comments', {
509 count: post.number_of_comments,
511 to={`/post/${post.id}`}
513 <svg class="mr-1 icon icon-inline">
514 <use xlinkHref="#icon-message-square"></use>
516 {post.number_of_comments}
520 {this.props.post.duplicates && (
521 <ul class="list-inline mb-1 small text-muted">
523 <li className="list-inline-item mr-2">
524 {i18n.t('cross_posted_to')}
526 {this.props.post.duplicates.map(post => (
527 <li className="list-inline-item mr-2">
528 <Link to={`/post/${post.id}`}>
529 {post.community_name}
536 <ul class="list-inline mb-1 text-muted font-weight-bold">
537 {UserService.Instance.user && (
539 {this.props.showBody && (
541 <li className="list-inline-item">
543 class="btn btn-sm btn-link btn-animate text-muted"
544 onClick={linkEvent(this, this.handleSavePostClick)}
546 post.saved ? i18n.t('unsave') : i18n.t('save')
550 class={`icon icon-inline ${
551 post.saved && 'text-warning'
554 <use xlinkHref="#icon-star"></use>
558 <li className="list-inline-item">
560 class="btn btn-sm btn-link btn-animate text-muted"
561 to={`/create_post${this.crossPostParams}`}
562 title={i18n.t('cross_post')}
564 <svg class="icon icon-inline">
565 <use xlinkHref="#icon-copy"></use>
571 {this.myPost && this.props.showBody && (
573 <li className="list-inline-item">
575 class="btn btn-sm btn-link btn-animate text-muted"
576 onClick={linkEvent(this, this.handleEditClick)}
577 data-tippy-content={i18n.t('edit')}
579 <svg class="icon icon-inline">
580 <use xlinkHref="#icon-edit"></use>
584 <li className="list-inline-item">
586 class="btn btn-sm btn-link btn-animate text-muted"
587 onClick={linkEvent(this, this.handleDeleteClick)}
595 class={`icon icon-inline ${
596 post.deleted && 'text-danger'
599 <use xlinkHref="#icon-trash"></use>
606 {!this.state.showAdvanced && this.props.showBody ? (
607 <li className="list-inline-item">
609 class="btn btn-sm btn-link btn-animate text-muted"
610 onClick={linkEvent(this, this.handleShowAdvanced)}
611 data-tippy-content={i18n.t('more')}
613 <svg class="icon icon-inline">
614 <use xlinkHref="#icon-more-vertical"></use>
620 {this.props.showBody && post.body && (
621 <li className="list-inline-item">
623 class="btn btn-sm btn-link btn-animate text-muted"
624 onClick={linkEvent(this, this.handleViewSource)}
625 data-tippy-content={i18n.t('view_source')}
628 class={`icon icon-inline ${
629 this.state.viewSource && 'text-success'
632 <use xlinkHref="#icon-file-text"></use>
637 {this.canModOnSelf && (
639 <li className="list-inline-item">
641 class="btn btn-sm btn-link btn-animate text-muted"
642 onClick={linkEvent(this, this.handleModLock)}
650 class={`icon icon-inline ${
651 post.locked && 'text-danger'
654 <use xlinkHref="#icon-lock"></use>
658 <li className="list-inline-item">
660 class="btn btn-sm btn-link btn-animate text-muted"
661 onClick={linkEvent(this, this.handleModSticky)}
669 class={`icon icon-inline ${
670 post.stickied && 'text-success'
673 <use xlinkHref="#icon-pin"></use>
679 {/* Mods can ban from community, and appoint as mods to community */}
680 {(this.canMod || this.canAdmin) && (
681 <li className="list-inline-item">
687 this.handleModRemoveShow
697 this.handleModRemoveSubmit
708 <li className="list-inline-item">
709 {!post.banned_from_community ? (
714 this.handleModBanFromCommunityShow
724 this.handleModBanFromCommunitySubmit
732 {!post.banned_from_community && (
733 <li className="list-inline-item">
738 this.handleAddModToCommunity
742 ? i18n.t('remove_as_mod')
743 : i18n.t('appoint_as_mod')}
749 {/* Community creators and admins can transfer community to another mod */}
750 {(this.amCommunityCreator || this.canAdmin) &&
752 <li className="list-inline-item">
753 {!this.state.showConfirmTransferCommunity ? (
758 this.handleShowConfirmTransferCommunity
761 {i18n.t('transfer_community')}
765 <span class="d-inline-block mr-1">
766 {i18n.t('are_you_sure')}
769 class="pointer d-inline-block mr-1"
772 this.handleTransferCommunity
778 class="pointer d-inline-block"
782 .handleCancelShowConfirmTransferCommunity
791 {/* Admins can ban from all, and appoint other admins */}
795 <li className="list-inline-item">
801 this.handleModBanShow
804 {i18n.t('ban_from_site')}
811 this.handleModBanSubmit
814 {i18n.t('unban_from_site')}
820 <li className="list-inline-item">
823 onClick={linkEvent(this, this.handleAddAdmin)}
826 ? i18n.t('remove_as_admin')
827 : i18n.t('appoint_as_admin')}
833 {/* Site Creator can transfer to another admin */}
834 {this.amSiteCreator && this.isAdmin && (
835 <li className="list-inline-item">
836 {!this.state.showConfirmTransferSite ? (
841 this.handleShowConfirmTransferSite
844 {i18n.t('transfer_site')}
848 <span class="d-inline-block mr-1">
849 {i18n.t('are_you_sure')}
852 class="pointer d-inline-block mr-1"
855 this.handleTransferSite
861 class="pointer d-inline-block"
864 this.handleCancelShowConfirmTransferSite
878 {this.state.showRemoveDialog && (
881 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
885 class="form-control mr-2"
886 placeholder={i18n.t('reason')}
887 value={this.state.removeReason}
888 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
890 <button type="submit" class="btn btn-secondary">
891 {i18n.t('remove_post')}
895 {this.state.showBanDialog && (
896 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
897 <div class="form-group row">
898 <label class="col-form-label" htmlFor="post-listing-reason">
903 id="post-listing-reason"
904 class="form-control mr-2"
905 placeholder={i18n.t('reason')}
906 value={this.state.banReason}
907 onInput={linkEvent(this, this.handleModBanReasonChange)}
910 {/* TODO hold off on expires until later */}
911 {/* <div class="form-group row"> */}
912 {/* <label class="col-form-label">Expires</label> */}
913 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
915 <div class="form-group row">
916 <button type="submit" class="btn btn-secondary">
917 {i18n.t('ban')} {post.creator_name}
929 private get myPost(): boolean {
931 UserService.Instance.user &&
932 this.props.post.creator_id == UserService.Instance.user.id
936 get isMod(): boolean {
938 this.props.moderators &&
940 this.props.moderators.map(m => m.user_id),
941 this.props.post.creator_id
946 get isAdmin(): boolean {
950 this.props.admins.map(a => a.id),
951 this.props.post.creator_id
956 get canMod(): boolean {
957 if (this.props.admins && this.props.moderators) {
958 let adminsThenMods = this.props.admins
960 .concat(this.props.moderators.map(m => m.user_id));
963 UserService.Instance.user,
965 this.props.post.creator_id
972 get canModOnSelf(): boolean {
973 if (this.props.admins && this.props.moderators) {
974 let adminsThenMods = this.props.admins
976 .concat(this.props.moderators.map(m => m.user_id));
979 UserService.Instance.user,
981 this.props.post.creator_id,
989 get canAdmin(): boolean {
993 UserService.Instance.user,
994 this.props.admins.map(a => a.id),
995 this.props.post.creator_id
1000 get amCommunityCreator(): boolean {
1002 this.props.moderators &&
1003 UserService.Instance.user &&
1004 this.props.post.creator_id != UserService.Instance.user.id &&
1005 UserService.Instance.user.id == this.props.moderators[0].user_id
1009 get amSiteCreator(): boolean {
1011 this.props.admins &&
1012 UserService.Instance.user &&
1013 this.props.post.creator_id != UserService.Instance.user.id &&
1014 UserService.Instance.user.id == this.props.admins[0].id
1018 handlePostLike(i: PostListing) {
1019 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1021 if (i.state.my_vote == 1) {
1024 } else if (i.state.my_vote == -1) {
1025 i.state.downvotes--;
1033 i.state.my_vote = new_vote;
1035 let form: CreatePostLikeForm = {
1036 post_id: i.props.post.id,
1037 score: i.state.my_vote,
1040 WebSocketService.Instance.likePost(form);
1041 i.setState(i.state);
1045 handlePostDisLike(i: PostListing) {
1046 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1048 if (i.state.my_vote == 1) {
1051 i.state.downvotes++;
1052 } else if (i.state.my_vote == -1) {
1053 i.state.downvotes--;
1056 i.state.downvotes++;
1060 i.state.my_vote = new_vote;
1062 let form: CreatePostLikeForm = {
1063 post_id: i.props.post.id,
1064 score: i.state.my_vote,
1067 WebSocketService.Instance.likePost(form);
1068 i.setState(i.state);
1072 handleEditClick(i: PostListing) {
1073 i.state.showEdit = true;
1074 i.setState(i.state);
1077 handleEditCancel() {
1078 this.state.showEdit = false;
1079 this.setState(this.state);
1082 // The actual editing is done in the recieve for post
1084 this.state.showEdit = false;
1085 this.setState(this.state);
1088 handleDeleteClick(i: PostListing) {
1089 let deleteForm: PostFormI = {
1090 body: i.props.post.body,
1091 community_id: i.props.post.community_id,
1092 name: i.props.post.name,
1093 url: i.props.post.url,
1094 edit_id: i.props.post.id,
1095 creator_id: i.props.post.creator_id,
1096 deleted: !i.props.post.deleted,
1097 nsfw: i.props.post.nsfw,
1100 WebSocketService.Instance.editPost(deleteForm);
1103 handleSavePostClick(i: PostListing) {
1104 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1105 let form: SavePostForm = {
1106 post_id: i.props.post.id,
1110 WebSocketService.Instance.savePost(form);
1113 get crossPostParams(): string {
1114 let params = `?title=${this.props.post.name}`;
1115 let post = this.props.post;
1118 params += `&url=${post.url}`;
1120 if (this.props.post.body) {
1121 params += `&body=${this.props.post.body}`;
1126 handleModRemoveShow(i: PostListing) {
1127 i.state.showRemoveDialog = true;
1128 i.setState(i.state);
1131 handleModRemoveReasonChange(i: PostListing, event: any) {
1132 i.state.removeReason = event.target.value;
1133 i.setState(i.state);
1136 handleModRemoveSubmit(i: PostListing) {
1137 event.preventDefault();
1138 let form: PostFormI = {
1139 name: i.props.post.name,
1140 community_id: i.props.post.community_id,
1141 edit_id: i.props.post.id,
1142 creator_id: i.props.post.creator_id,
1143 removed: !i.props.post.removed,
1144 reason: i.state.removeReason,
1145 nsfw: i.props.post.nsfw,
1148 WebSocketService.Instance.editPost(form);
1150 i.state.showRemoveDialog = false;
1151 i.setState(i.state);
1154 handleModLock(i: PostListing) {
1155 let form: PostFormI = {
1156 name: i.props.post.name,
1157 community_id: i.props.post.community_id,
1158 edit_id: i.props.post.id,
1159 creator_id: i.props.post.creator_id,
1160 nsfw: i.props.post.nsfw,
1161 locked: !i.props.post.locked,
1164 WebSocketService.Instance.editPost(form);
1167 handleModSticky(i: PostListing) {
1168 let form: PostFormI = {
1169 name: i.props.post.name,
1170 community_id: i.props.post.community_id,
1171 edit_id: i.props.post.id,
1172 creator_id: i.props.post.creator_id,
1173 nsfw: i.props.post.nsfw,
1174 stickied: !i.props.post.stickied,
1177 WebSocketService.Instance.editPost(form);
1180 handleModBanFromCommunityShow(i: PostListing) {
1181 i.state.showBanDialog = true;
1182 i.state.banType = BanType.Community;
1183 i.setState(i.state);
1186 handleModBanShow(i: PostListing) {
1187 i.state.showBanDialog = true;
1188 i.state.banType = BanType.Site;
1189 i.setState(i.state);
1192 handleModBanReasonChange(i: PostListing, event: any) {
1193 i.state.banReason = event.target.value;
1194 i.setState(i.state);
1197 handleModBanExpiresChange(i: PostListing, event: any) {
1198 i.state.banExpires = event.target.value;
1199 i.setState(i.state);
1202 handleModBanFromCommunitySubmit(i: PostListing) {
1203 i.state.banType = BanType.Community;
1204 i.setState(i.state);
1205 i.handleModBanBothSubmit(i);
1208 handleModBanSubmit(i: PostListing) {
1209 i.state.banType = BanType.Site;
1210 i.setState(i.state);
1211 i.handleModBanBothSubmit(i);
1214 handleModBanBothSubmit(i: PostListing) {
1215 event.preventDefault();
1217 if (i.state.banType == BanType.Community) {
1218 let form: BanFromCommunityForm = {
1219 user_id: i.props.post.creator_id,
1220 community_id: i.props.post.community_id,
1221 ban: !i.props.post.banned_from_community,
1222 reason: i.state.banReason,
1223 expires: getUnixTime(i.state.banExpires),
1225 WebSocketService.Instance.banFromCommunity(form);
1227 let form: BanUserForm = {
1228 user_id: i.props.post.creator_id,
1229 ban: !i.props.post.banned,
1230 reason: i.state.banReason,
1231 expires: getUnixTime(i.state.banExpires),
1233 WebSocketService.Instance.banUser(form);
1236 i.state.showBanDialog = false;
1237 i.setState(i.state);
1240 handleAddModToCommunity(i: PostListing) {
1241 let form: AddModToCommunityForm = {
1242 user_id: i.props.post.creator_id,
1243 community_id: i.props.post.community_id,
1246 WebSocketService.Instance.addModToCommunity(form);
1247 i.setState(i.state);
1250 handleAddAdmin(i: PostListing) {
1251 let form: AddAdminForm = {
1252 user_id: i.props.post.creator_id,
1255 WebSocketService.Instance.addAdmin(form);
1256 i.setState(i.state);
1259 handleShowConfirmTransferCommunity(i: PostListing) {
1260 i.state.showConfirmTransferCommunity = true;
1261 i.setState(i.state);
1264 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1265 i.state.showConfirmTransferCommunity = false;
1266 i.setState(i.state);
1269 handleTransferCommunity(i: PostListing) {
1270 let form: TransferCommunityForm = {
1271 community_id: i.props.post.community_id,
1272 user_id: i.props.post.creator_id,
1274 WebSocketService.Instance.transferCommunity(form);
1275 i.state.showConfirmTransferCommunity = false;
1276 i.setState(i.state);
1279 handleShowConfirmTransferSite(i: PostListing) {
1280 i.state.showConfirmTransferSite = true;
1281 i.setState(i.state);
1284 handleCancelShowConfirmTransferSite(i: PostListing) {
1285 i.state.showConfirmTransferSite = false;
1286 i.setState(i.state);
1289 handleTransferSite(i: PostListing) {
1290 let form: TransferSiteForm = {
1291 user_id: i.props.post.creator_id,
1293 WebSocketService.Instance.transferSite(form);
1294 i.state.showConfirmTransferSite = false;
1295 i.setState(i.state);
1298 handleImageExpandClick(i: PostListing) {
1299 i.state.imageExpanded = !i.state.imageExpanded;
1300 i.setState(i.state);
1303 handleViewSource(i: PostListing) {
1304 i.state.viewSource = !i.state.viewSource;
1305 i.setState(i.state);
1308 handleShowAdvanced(i: PostListing) {
1309 i.state.showAdvanced = !i.state.showAdvanced;
1310 i.setState(i.state);
1314 get pointsTippy(): string {
1315 let points = i18n.t('number_of_points', {
1316 count: this.state.score,
1319 let upvotes = i18n.t('number_of_upvotes', {
1320 count: this.state.upvotes,
1323 let downvotes = i18n.t('number_of_downvotes', {
1324 count: this.state.downvotes,
1327 return `${points} • ${upvotes} • ${downvotes}`;