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);
168 } else if (post.thumbnail_url) {
169 return pictshareImage(post.thumbnail_url, thumbnail);
173 } else if (post.thumbnail_url) {
174 return pictshareImage(post.thumbnail_url, thumbnail);
179 let post = this.props.post;
181 if (isImage(post.url)) {
184 class="text-body pointer"
185 data-tippy-content={i18n.t('expand_here')}
186 onClick={linkEvent(this, this.handleImageExpandClick)}
188 {this.imgThumb(this.getImage(true))}
189 <svg class="icon mini-overlay">
190 <use xlinkHref="#icon-image"></use>
194 } else if (post.thumbnail_url) {
197 className="text-body"
202 {this.imgThumb(this.getImage(true))}
203 <svg class="icon mini-overlay">
204 <use xlinkHref="#icon-external-link"></use>
208 } else if (post.url) {
209 if (isVideo(post.url)) {
211 <div class="embed-responsive embed-responsive-16by9">
217 class="embed-responsive-item"
219 <source src={post.url} type="video/mp4" />
226 className="text-body"
231 <svg class="icon thumbnail">
232 <use xlinkHref="#icon-external-link"></use>
240 className="text-body"
241 to={`/post/${post.id}`}
242 title={i18n.t('comments')}
244 <svg class="icon thumbnail">
245 <use xlinkHref="#icon-message-square"></use>
253 let post = this.props.post;
256 <div className={`vote-bar col-1 pr-0 small text-center`}>
258 className={`btn-animate btn btn-link p-0 ${
259 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
261 onClick={linkEvent(this, this.handlePostLike)}
262 data-tippy-content={i18n.t('upvote')}
264 <svg class="icon upvote">
265 <use xlinkHref="#icon-arrow-up1"></use>
269 class={`unselectable pointer font-weight-bold text-muted px-1`}
270 data-tippy-content={this.pointsTippy}
274 {WebSocketService.Instance.site.enable_downvotes && (
276 className={`btn-animate btn btn-link p-0 ${
277 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
279 onClick={linkEvent(this, this.handlePostDisLike)}
280 data-tippy-content={i18n.t('downvote')}
282 <svg class="icon downvote">
283 <use xlinkHref="#icon-arrow-down1"></use>
288 {!this.state.imageExpanded && (
289 <div class="col-3 col-sm-2 pr-0 mt-1">
290 <div class="position-relative">{this.thumbnail()}</div>
294 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
297 <div className="col-12">
298 <div className="post-title">
299 <h5 className="mb-0 d-inline">
300 {this.props.showBody && post.url ? (
302 className="text-body"
311 className="text-body"
312 to={`/post/${post.id}`}
313 title={i18n.t('comments')}
319 {post.url && !(hostname(post.url) == window.location.hostname) && (
320 <small class="d-inline-block">
322 className="ml-2 text-muted font-italic"
328 <svg class="ml-1 icon icon-inline">
329 <use xlinkHref="#icon-external-link"></use>
334 {(isImage(post.url) || this.props.post.thumbnail_url) && (
336 {!this.state.imageExpanded ? (
338 class="text-monospace unselectable pointer ml-2 text-muted small"
339 data-tippy-content={i18n.t('expand_here')}
340 onClick={linkEvent(this, this.handleImageExpandClick)}
342 <svg class="icon icon-inline">
343 <use xlinkHref="#icon-plus-square"></use>
349 class="text-monospace unselectable pointer ml-2 text-muted small"
350 onClick={linkEvent(this, this.handleImageExpandClick)}
352 <svg class="icon icon-inline">
353 <use xlinkHref="#icon-minus-square"></use>
361 this.handleImageExpandClick
365 class="img-fluid img-expanded"
366 src={this.getImage()}
375 <small className="ml-2 text-muted font-italic">
381 className="unselectable pointer ml-2 text-muted font-italic"
382 data-tippy-content={i18n.t('deleted')}
384 <svg class={`icon icon-inline text-danger`}>
385 <use xlinkHref="#icon-trash"></use>
391 className="unselectable pointer ml-2 text-muted font-italic"
392 data-tippy-content={i18n.t('locked')}
394 <svg class={`icon icon-inline text-danger`}>
395 <use xlinkHref="#icon-lock"></use>
401 className="unselectable pointer ml-2 text-muted font-italic"
402 data-tippy-content={i18n.t('stickied')}
404 <svg class={`icon icon-inline text-success`}>
405 <use xlinkHref="#icon-pin"></use>
410 <small className="ml-2 text-muted font-italic">
418 <div className="details col-12">
419 <ul class="list-inline mb-0 text-muted small">
420 <li className="list-inline-item">
421 <span>{i18n.t('by')} </span>
424 name: post.creator_name,
425 avatar: post.creator_avatar,
427 local: post.creator_local,
428 actor_id: post.creator_actor_id,
432 <span className="mx-1 badge badge-light">
437 <span className="mx-1 badge badge-light">
441 {(post.banned_from_community || post.banned) && (
442 <span className="mx-1 badge badge-danger">
446 {this.props.showCommunity && (
448 <span> {i18n.t('to')} </span>
451 name: post.community_name,
452 id: post.community_id,
453 local: post.community_local,
454 actor_id: post.community_actor_id,
460 <li className="list-inline-item">•</li>
461 <li className="list-inline-item">
463 <MomentTime data={post} />
468 <li className="list-inline-item">•</li>
469 <li className="list-inline-item">
470 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
472 className="text-muted"
473 data-tippy-content={md.render(previewLines(post.body))}
474 data-tippy-allowHtml={true}
475 to={`/post/${post.id}`}
477 <svg class="mr-1 icon icon-inline">
478 <use xlinkHref="#icon-book-open"></use>
484 <li className="list-inline-item">•</li>
485 {this.state.upvotes !== this.state.score && (
488 class="unselectable pointer mr-2"
489 data-tippy-content={this.pointsTippy}
491 <li className="list-inline-item">
492 <span className="text-muted">
493 <svg class="small icon icon-inline mr-1">
494 <use xlinkHref="#icon-arrow-up"></use>
499 <li className="list-inline-item">
500 <span className="text-muted">
501 <svg class="small icon icon-inline mr-1">
502 <use xlinkHref="#icon-arrow-down"></use>
504 {this.state.downvotes}
508 <li className="list-inline-item">•</li>
511 <li className="list-inline-item">
513 className="text-muted"
514 title={i18n.t('number_of_comments', {
515 count: post.number_of_comments,
517 to={`/post/${post.id}`}
519 <svg class="mr-1 icon icon-inline">
520 <use xlinkHref="#icon-message-square"></use>
522 {post.number_of_comments}
526 {this.props.post.duplicates && (
527 <ul class="list-inline mb-1 small text-muted">
529 <li className="list-inline-item mr-2">
530 {i18n.t('cross_posted_to')}
532 {this.props.post.duplicates.map(post => (
533 <li className="list-inline-item mr-2">
534 <Link to={`/post/${post.id}`}>
535 {post.community_name}
542 <ul class="list-inline mb-1 text-muted font-weight-bold">
543 {UserService.Instance.user && (
545 {this.props.showBody && (
547 <li className="list-inline-item">
549 class="btn btn-sm btn-link btn-animate text-muted"
550 onClick={linkEvent(this, this.handleSavePostClick)}
552 post.saved ? i18n.t('unsave') : i18n.t('save')
556 class={`icon icon-inline ${
557 post.saved && 'text-warning'
560 <use xlinkHref="#icon-star"></use>
564 <li className="list-inline-item">
566 class="btn btn-sm btn-link btn-animate text-muted"
567 to={`/create_post${this.crossPostParams}`}
568 title={i18n.t('cross_post')}
570 <svg class="icon icon-inline">
571 <use xlinkHref="#icon-copy"></use>
577 {this.myPost && this.props.showBody && (
579 <li className="list-inline-item">
581 class="btn btn-sm btn-link btn-animate text-muted"
582 onClick={linkEvent(this, this.handleEditClick)}
583 data-tippy-content={i18n.t('edit')}
585 <svg class="icon icon-inline">
586 <use xlinkHref="#icon-edit"></use>
590 <li className="list-inline-item">
592 class="btn btn-sm btn-link btn-animate text-muted"
593 onClick={linkEvent(this, this.handleDeleteClick)}
601 class={`icon icon-inline ${
602 post.deleted && 'text-danger'
605 <use xlinkHref="#icon-trash"></use>
612 {!this.state.showAdvanced && this.props.showBody ? (
613 <li className="list-inline-item">
615 class="btn btn-sm btn-link btn-animate text-muted"
616 onClick={linkEvent(this, this.handleShowAdvanced)}
617 data-tippy-content={i18n.t('more')}
619 <svg class="icon icon-inline">
620 <use xlinkHref="#icon-more-vertical"></use>
626 {this.props.showBody && post.body && (
627 <li className="list-inline-item">
629 class="btn btn-sm btn-link btn-animate text-muted"
630 onClick={linkEvent(this, this.handleViewSource)}
631 data-tippy-content={i18n.t('view_source')}
634 class={`icon icon-inline ${
635 this.state.viewSource && 'text-success'
638 <use xlinkHref="#icon-file-text"></use>
643 {this.canModOnSelf && (
645 <li className="list-inline-item">
647 class="btn btn-sm btn-link btn-animate text-muted"
648 onClick={linkEvent(this, this.handleModLock)}
656 class={`icon icon-inline ${
657 post.locked && 'text-danger'
660 <use xlinkHref="#icon-lock"></use>
664 <li className="list-inline-item">
666 class="btn btn-sm btn-link btn-animate text-muted"
667 onClick={linkEvent(this, this.handleModSticky)}
675 class={`icon icon-inline ${
676 post.stickied && 'text-success'
679 <use xlinkHref="#icon-pin"></use>
685 {/* Mods can ban from community, and appoint as mods to community */}
686 {(this.canMod || this.canAdmin) && (
687 <li className="list-inline-item">
693 this.handleModRemoveShow
703 this.handleModRemoveSubmit
714 <li className="list-inline-item">
715 {!post.banned_from_community ? (
720 this.handleModBanFromCommunityShow
730 this.handleModBanFromCommunitySubmit
738 {!post.banned_from_community && (
739 <li className="list-inline-item">
744 this.handleAddModToCommunity
748 ? i18n.t('remove_as_mod')
749 : i18n.t('appoint_as_mod')}
755 {/* Community creators and admins can transfer community to another mod */}
756 {(this.amCommunityCreator || this.canAdmin) &&
758 <li className="list-inline-item">
759 {!this.state.showConfirmTransferCommunity ? (
764 this.handleShowConfirmTransferCommunity
767 {i18n.t('transfer_community')}
771 <span class="d-inline-block mr-1">
772 {i18n.t('are_you_sure')}
775 class="pointer d-inline-block mr-1"
778 this.handleTransferCommunity
784 class="pointer d-inline-block"
788 .handleCancelShowConfirmTransferCommunity
797 {/* Admins can ban from all, and appoint other admins */}
801 <li className="list-inline-item">
807 this.handleModBanShow
810 {i18n.t('ban_from_site')}
817 this.handleModBanSubmit
820 {i18n.t('unban_from_site')}
826 <li className="list-inline-item">
829 onClick={linkEvent(this, this.handleAddAdmin)}
832 ? i18n.t('remove_as_admin')
833 : i18n.t('appoint_as_admin')}
839 {/* Site Creator can transfer to another admin */}
840 {this.amSiteCreator && this.isAdmin && (
841 <li className="list-inline-item">
842 {!this.state.showConfirmTransferSite ? (
847 this.handleShowConfirmTransferSite
850 {i18n.t('transfer_site')}
854 <span class="d-inline-block mr-1">
855 {i18n.t('are_you_sure')}
858 class="pointer d-inline-block mr-1"
861 this.handleTransferSite
867 class="pointer d-inline-block"
870 this.handleCancelShowConfirmTransferSite
884 {this.state.showRemoveDialog && (
887 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
891 class="form-control mr-2"
892 placeholder={i18n.t('reason')}
893 value={this.state.removeReason}
894 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
896 <button type="submit" class="btn btn-secondary">
897 {i18n.t('remove_post')}
901 {this.state.showBanDialog && (
902 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
903 <div class="form-group row">
904 <label class="col-form-label" htmlFor="post-listing-reason">
909 id="post-listing-reason"
910 class="form-control mr-2"
911 placeholder={i18n.t('reason')}
912 value={this.state.banReason}
913 onInput={linkEvent(this, this.handleModBanReasonChange)}
916 {/* TODO hold off on expires until later */}
917 {/* <div class="form-group row"> */}
918 {/* <label class="col-form-label">Expires</label> */}
919 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
921 <div class="form-group row">
922 <button type="submit" class="btn btn-secondary">
923 {i18n.t('ban')} {post.creator_name}
935 private get myPost(): boolean {
937 UserService.Instance.user &&
938 this.props.post.creator_id == UserService.Instance.user.id
942 get isMod(): boolean {
944 this.props.moderators &&
946 this.props.moderators.map(m => m.user_id),
947 this.props.post.creator_id
952 get isAdmin(): boolean {
956 this.props.admins.map(a => a.id),
957 this.props.post.creator_id
962 get canMod(): boolean {
963 if (this.props.admins && this.props.moderators) {
964 let adminsThenMods = this.props.admins
966 .concat(this.props.moderators.map(m => m.user_id));
969 UserService.Instance.user,
971 this.props.post.creator_id
978 get canModOnSelf(): boolean {
979 if (this.props.admins && this.props.moderators) {
980 let adminsThenMods = this.props.admins
982 .concat(this.props.moderators.map(m => m.user_id));
985 UserService.Instance.user,
987 this.props.post.creator_id,
995 get canAdmin(): boolean {
999 UserService.Instance.user,
1000 this.props.admins.map(a => a.id),
1001 this.props.post.creator_id
1006 get amCommunityCreator(): boolean {
1008 this.props.moderators &&
1009 UserService.Instance.user &&
1010 this.props.post.creator_id != UserService.Instance.user.id &&
1011 UserService.Instance.user.id == this.props.moderators[0].user_id
1015 get amSiteCreator(): boolean {
1017 this.props.admins &&
1018 UserService.Instance.user &&
1019 this.props.post.creator_id != UserService.Instance.user.id &&
1020 UserService.Instance.user.id == this.props.admins[0].id
1024 handlePostLike(i: PostListing) {
1025 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1027 if (i.state.my_vote == 1) {
1030 } else if (i.state.my_vote == -1) {
1031 i.state.downvotes--;
1039 i.state.my_vote = new_vote;
1041 let form: CreatePostLikeForm = {
1042 post_id: i.props.post.id,
1043 score: i.state.my_vote,
1046 WebSocketService.Instance.likePost(form);
1047 i.setState(i.state);
1051 handlePostDisLike(i: PostListing) {
1052 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1054 if (i.state.my_vote == 1) {
1057 i.state.downvotes++;
1058 } else if (i.state.my_vote == -1) {
1059 i.state.downvotes--;
1062 i.state.downvotes++;
1066 i.state.my_vote = new_vote;
1068 let form: CreatePostLikeForm = {
1069 post_id: i.props.post.id,
1070 score: i.state.my_vote,
1073 WebSocketService.Instance.likePost(form);
1074 i.setState(i.state);
1078 handleEditClick(i: PostListing) {
1079 i.state.showEdit = true;
1080 i.setState(i.state);
1083 handleEditCancel() {
1084 this.state.showEdit = false;
1085 this.setState(this.state);
1088 // The actual editing is done in the recieve for post
1090 this.state.showEdit = false;
1091 this.setState(this.state);
1094 handleDeleteClick(i: PostListing) {
1095 let deleteForm: PostFormI = {
1096 body: i.props.post.body,
1097 community_id: i.props.post.community_id,
1098 name: i.props.post.name,
1099 url: i.props.post.url,
1100 edit_id: i.props.post.id,
1101 creator_id: i.props.post.creator_id,
1102 deleted: !i.props.post.deleted,
1103 nsfw: i.props.post.nsfw,
1106 WebSocketService.Instance.editPost(deleteForm);
1109 handleSavePostClick(i: PostListing) {
1110 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1111 let form: SavePostForm = {
1112 post_id: i.props.post.id,
1116 WebSocketService.Instance.savePost(form);
1119 get crossPostParams(): string {
1120 let params = `?title=${this.props.post.name}`;
1121 let post = this.props.post;
1124 params += `&url=${post.url}`;
1126 if (this.props.post.body) {
1127 params += `&body=${this.props.post.body}`;
1132 handleModRemoveShow(i: PostListing) {
1133 i.state.showRemoveDialog = true;
1134 i.setState(i.state);
1137 handleModRemoveReasonChange(i: PostListing, event: any) {
1138 i.state.removeReason = event.target.value;
1139 i.setState(i.state);
1142 handleModRemoveSubmit(i: PostListing) {
1143 event.preventDefault();
1144 let form: PostFormI = {
1145 name: i.props.post.name,
1146 community_id: i.props.post.community_id,
1147 edit_id: i.props.post.id,
1148 creator_id: i.props.post.creator_id,
1149 removed: !i.props.post.removed,
1150 reason: i.state.removeReason,
1151 nsfw: i.props.post.nsfw,
1154 WebSocketService.Instance.editPost(form);
1156 i.state.showRemoveDialog = false;
1157 i.setState(i.state);
1160 handleModLock(i: PostListing) {
1161 let form: PostFormI = {
1162 name: i.props.post.name,
1163 community_id: i.props.post.community_id,
1164 edit_id: i.props.post.id,
1165 creator_id: i.props.post.creator_id,
1166 nsfw: i.props.post.nsfw,
1167 locked: !i.props.post.locked,
1170 WebSocketService.Instance.editPost(form);
1173 handleModSticky(i: PostListing) {
1174 let form: PostFormI = {
1175 name: i.props.post.name,
1176 community_id: i.props.post.community_id,
1177 edit_id: i.props.post.id,
1178 creator_id: i.props.post.creator_id,
1179 nsfw: i.props.post.nsfw,
1180 stickied: !i.props.post.stickied,
1183 WebSocketService.Instance.editPost(form);
1186 handleModBanFromCommunityShow(i: PostListing) {
1187 i.state.showBanDialog = true;
1188 i.state.banType = BanType.Community;
1189 i.setState(i.state);
1192 handleModBanShow(i: PostListing) {
1193 i.state.showBanDialog = true;
1194 i.state.banType = BanType.Site;
1195 i.setState(i.state);
1198 handleModBanReasonChange(i: PostListing, event: any) {
1199 i.state.banReason = event.target.value;
1200 i.setState(i.state);
1203 handleModBanExpiresChange(i: PostListing, event: any) {
1204 i.state.banExpires = event.target.value;
1205 i.setState(i.state);
1208 handleModBanFromCommunitySubmit(i: PostListing) {
1209 i.state.banType = BanType.Community;
1210 i.setState(i.state);
1211 i.handleModBanBothSubmit(i);
1214 handleModBanSubmit(i: PostListing) {
1215 i.state.banType = BanType.Site;
1216 i.setState(i.state);
1217 i.handleModBanBothSubmit(i);
1220 handleModBanBothSubmit(i: PostListing) {
1221 event.preventDefault();
1223 if (i.state.banType == BanType.Community) {
1224 let form: BanFromCommunityForm = {
1225 user_id: i.props.post.creator_id,
1226 community_id: i.props.post.community_id,
1227 ban: !i.props.post.banned_from_community,
1228 reason: i.state.banReason,
1229 expires: getUnixTime(i.state.banExpires),
1231 WebSocketService.Instance.banFromCommunity(form);
1233 let form: BanUserForm = {
1234 user_id: i.props.post.creator_id,
1235 ban: !i.props.post.banned,
1236 reason: i.state.banReason,
1237 expires: getUnixTime(i.state.banExpires),
1239 WebSocketService.Instance.banUser(form);
1242 i.state.showBanDialog = false;
1243 i.setState(i.state);
1246 handleAddModToCommunity(i: PostListing) {
1247 let form: AddModToCommunityForm = {
1248 user_id: i.props.post.creator_id,
1249 community_id: i.props.post.community_id,
1252 WebSocketService.Instance.addModToCommunity(form);
1253 i.setState(i.state);
1256 handleAddAdmin(i: PostListing) {
1257 let form: AddAdminForm = {
1258 user_id: i.props.post.creator_id,
1261 WebSocketService.Instance.addAdmin(form);
1262 i.setState(i.state);
1265 handleShowConfirmTransferCommunity(i: PostListing) {
1266 i.state.showConfirmTransferCommunity = true;
1267 i.setState(i.state);
1270 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1271 i.state.showConfirmTransferCommunity = false;
1272 i.setState(i.state);
1275 handleTransferCommunity(i: PostListing) {
1276 let form: TransferCommunityForm = {
1277 community_id: i.props.post.community_id,
1278 user_id: i.props.post.creator_id,
1280 WebSocketService.Instance.transferCommunity(form);
1281 i.state.showConfirmTransferCommunity = false;
1282 i.setState(i.state);
1285 handleShowConfirmTransferSite(i: PostListing) {
1286 i.state.showConfirmTransferSite = true;
1287 i.setState(i.state);
1290 handleCancelShowConfirmTransferSite(i: PostListing) {
1291 i.state.showConfirmTransferSite = false;
1292 i.setState(i.state);
1295 handleTransferSite(i: PostListing) {
1296 let form: TransferSiteForm = {
1297 user_id: i.props.post.creator_id,
1299 WebSocketService.Instance.transferSite(form);
1300 i.state.showConfirmTransferSite = false;
1301 i.setState(i.state);
1304 handleImageExpandClick(i: PostListing) {
1305 i.state.imageExpanded = !i.state.imageExpanded;
1306 i.setState(i.state);
1309 handleViewSource(i: PostListing) {
1310 i.state.viewSource = !i.state.viewSource;
1311 i.setState(i.state);
1314 handleShowAdvanced(i: PostListing) {
1315 i.state.showAdvanced = !i.state.showAdvanced;
1316 i.setState(i.state);
1320 get pointsTippy(): string {
1321 let points = i18n.t('number_of_points', {
1322 count: this.state.score,
1325 let upvotes = i18n.t('number_of_upvotes', {
1326 count: this.state.upvotes,
1329 let downvotes = i18n.t('number_of_downvotes', {
1330 count: this.state.downvotes,
1333 return `${points} • ${upvotes} • ${downvotes}`;