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>;
64 enableDownvotes: boolean;
68 export class PostListing extends Component<PostListingProps, PostListingState> {
69 private emptyState: PostListingState = {
71 showRemoveDialog: false,
76 banType: BanType.Community,
77 showConfirmTransferSite: false,
78 showConfirmTransferCommunity: false,
82 my_vote: this.props.post.my_vote,
83 score: this.props.post.score,
84 upvotes: this.props.post.upvotes,
85 downvotes: this.props.post.downvotes,
88 constructor(props: any, context: any) {
89 super(props, context);
91 this.state = this.emptyState;
92 this.handlePostLike = this.handlePostLike.bind(this);
93 this.handlePostDisLike = this.handlePostDisLike.bind(this);
94 this.handleEditPost = this.handleEditPost.bind(this);
95 this.handleEditCancel = this.handleEditCancel.bind(this);
98 componentWillReceiveProps(nextProps: PostListingProps) {
99 this.state.my_vote = nextProps.post.my_vote;
100 this.state.upvotes = nextProps.post.upvotes;
101 this.state.downvotes = nextProps.post.downvotes;
102 this.state.score = nextProps.post.score;
103 this.setState(this.state);
109 {!this.state.showEdit ? (
117 post={this.props.post}
118 onEdit={this.handleEditPost}
119 onCancel={this.handleEditCancel}
120 enableNsfw={this.props.enableNsfw}
121 enableDownvotes={this.props.enableDownvotes}
133 {this.props.post.url &&
134 this.props.showBody &&
135 this.props.post.embed_title && (
136 <IFramelyCard post={this.props.post} />
138 {this.props.showBody && this.props.post.body && (
140 {this.state.viewSource ? (
141 <pre>{this.props.post.body}</pre>
145 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
155 imgThumb(src: string) {
156 let post = this.props.post;
159 className={`img-fluid thumbnail rounded ${
160 (post.nsfw || post.community_nsfw) && 'img-blur'
167 getImage(thumbnail: boolean = false) {
168 let post = this.props.post;
169 if (isImage(post.url)) {
170 if (post.url.includes('pictrs')) {
171 return pictrsImage(post.url, thumbnail);
172 } else if (post.thumbnail_url) {
173 return pictrsImage(post.thumbnail_url, thumbnail);
177 } else if (post.thumbnail_url) {
178 return pictrsImage(post.thumbnail_url, thumbnail);
183 let post = this.props.post;
185 if (isImage(post.url)) {
188 class="text-body pointer"
189 data-tippy-content={i18n.t('expand_here')}
190 onClick={linkEvent(this, this.handleImageExpandClick)}
192 {this.imgThumb(this.getImage(true))}
193 <svg class="icon mini-overlay">
194 <use xlinkHref="#icon-image"></use>
198 } else if (post.thumbnail_url) {
201 className="text-body"
207 {this.imgThumb(this.getImage(true))}
208 <svg class="icon mini-overlay">
209 <use xlinkHref="#icon-external-link"></use>
213 } else if (post.url) {
214 if (isVideo(post.url)) {
216 <div class="embed-responsive embed-responsive-16by9">
222 class="embed-responsive-item"
224 <source src={post.url} type="video/mp4" />
231 className="text-body"
237 <svg class="icon thumbnail">
238 <use xlinkHref="#icon-external-link"></use>
246 className="text-body"
247 to={`/post/${post.id}`}
248 title={i18n.t('comments')}
250 <svg class="icon thumbnail">
251 <use xlinkHref="#icon-message-square"></use>
259 let post = this.props.post;
262 <div className={`vote-bar col-1 pr-0 small text-center`}>
264 className={`btn-animate btn btn-link p-0 ${
265 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
267 onClick={linkEvent(this, this.handlePostLike)}
268 data-tippy-content={i18n.t('upvote')}
270 <svg class="icon upvote">
271 <use xlinkHref="#icon-arrow-up1"></use>
275 class={`unselectable pointer font-weight-bold text-muted px-1`}
276 data-tippy-content={this.pointsTippy}
280 {this.props.enableDownvotes && (
282 className={`btn-animate btn btn-link p-0 ${
283 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
285 onClick={linkEvent(this, this.handlePostDisLike)}
286 data-tippy-content={i18n.t('downvote')}
288 <svg class="icon downvote">
289 <use xlinkHref="#icon-arrow-down1"></use>
294 {!this.state.imageExpanded && (
295 <div class="col-3 col-sm-2 pr-0 mt-1">
296 <div class="position-relative">{this.thumbnail()}</div>
300 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
303 <div className="col-12">
304 <div className="post-title">
305 <h5 className="mb-0 d-inline">
306 {this.props.showBody && post.url ? (
308 className="text-body"
318 className="text-body"
319 to={`/post/${post.id}`}
320 title={i18n.t('comments')}
326 {post.url && !(hostname(post.url) == window.location.hostname) && (
327 <small class="d-inline-block">
329 className="ml-2 text-muted font-italic"
336 <svg class="ml-1 icon icon-inline">
337 <use xlinkHref="#icon-external-link"></use>
342 {(isImage(post.url) || this.props.post.thumbnail_url) && (
344 {!this.state.imageExpanded ? (
346 class="text-monospace unselectable pointer ml-2 text-muted small"
347 data-tippy-content={i18n.t('expand_here')}
348 onClick={linkEvent(this, this.handleImageExpandClick)}
350 <svg class="icon icon-inline">
351 <use xlinkHref="#icon-plus-square"></use>
357 class="text-monospace unselectable pointer ml-2 text-muted small"
358 onClick={linkEvent(this, this.handleImageExpandClick)}
360 <svg class="icon icon-inline">
361 <use xlinkHref="#icon-minus-square"></use>
369 this.handleImageExpandClick
373 class="img-fluid img-expanded"
374 src={this.getImage()}
383 <small className="ml-2 text-muted font-italic">
389 className="unselectable pointer ml-2 text-muted font-italic"
390 data-tippy-content={i18n.t('deleted')}
392 <svg class={`icon icon-inline text-danger`}>
393 <use xlinkHref="#icon-trash"></use>
399 className="unselectable pointer ml-2 text-muted font-italic"
400 data-tippy-content={i18n.t('locked')}
402 <svg class={`icon icon-inline text-danger`}>
403 <use xlinkHref="#icon-lock"></use>
409 className="unselectable pointer ml-2 text-muted font-italic"
410 data-tippy-content={i18n.t('stickied')}
412 <svg class={`icon icon-inline text-success`}>
413 <use xlinkHref="#icon-pin"></use>
418 <small className="ml-2 text-muted font-italic">
426 <div className="details col-12">
427 <ul class="list-inline mb-0 text-muted small">
428 <li className="list-inline-item">
429 <span>{i18n.t('by')} </span>
432 name: post.creator_name,
433 avatar: post.creator_avatar,
435 local: post.creator_local,
436 actor_id: post.creator_actor_id,
437 published: post.creator_published,
442 <span className="mx-1 badge badge-light">
447 <span className="mx-1 badge badge-light">
451 {(post.banned_from_community || post.banned) && (
452 <span className="mx-1 badge badge-danger">
456 {this.props.showCommunity && (
458 <span> {i18n.t('to')} </span>
461 name: post.community_name,
462 id: post.community_id,
463 local: post.community_local,
464 actor_id: post.community_actor_id,
470 <li className="list-inline-item">•</li>
471 <li className="list-inline-item">
473 <MomentTime data={post} />
478 <li className="list-inline-item">•</li>
479 <li className="list-inline-item">
480 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
482 className="text-muted"
483 data-tippy-content={md.render(previewLines(post.body))}
484 data-tippy-allowHtml={true}
485 to={`/post/${post.id}`}
487 <svg class="mr-1 icon icon-inline">
488 <use xlinkHref="#icon-book-open"></use>
494 <li className="list-inline-item">•</li>
495 {this.state.upvotes !== this.state.score && (
498 class="unselectable pointer mr-2"
499 data-tippy-content={this.pointsTippy}
501 <li className="list-inline-item">
502 <span className="text-muted">
503 <svg class="small icon icon-inline mr-1">
504 <use xlinkHref="#icon-arrow-up"></use>
509 <li className="list-inline-item">
510 <span className="text-muted">
511 <svg class="small icon icon-inline mr-1">
512 <use xlinkHref="#icon-arrow-down"></use>
514 {this.state.downvotes}
518 <li className="list-inline-item">•</li>
521 <li className="list-inline-item">
523 className="text-muted"
524 title={i18n.t('number_of_comments', {
525 count: post.number_of_comments,
527 to={`/post/${post.id}`}
529 <svg class="mr-1 icon icon-inline">
530 <use xlinkHref="#icon-message-square"></use>
532 {post.number_of_comments}
536 {this.props.post.duplicates && (
537 <ul class="list-inline mb-1 small text-muted">
539 <li className="list-inline-item mr-2">
540 {i18n.t('cross_posted_to')}
542 {this.props.post.duplicates.map(post => (
543 <li className="list-inline-item mr-2">
544 <Link to={`/post/${post.id}`}>
545 {post.community_name}
552 <ul class="list-inline mb-1 text-muted font-weight-bold">
553 {UserService.Instance.user && (
555 {this.props.showBody && (
557 <li className="list-inline-item">
559 class="btn btn-sm btn-link btn-animate text-muted"
560 onClick={linkEvent(this, this.handleSavePostClick)}
562 post.saved ? i18n.t('unsave') : i18n.t('save')
566 class={`icon icon-inline ${
567 post.saved && 'text-warning'
570 <use xlinkHref="#icon-star"></use>
574 <li className="list-inline-item">
576 class="btn btn-sm btn-link btn-animate text-muted"
577 to={`/create_post${this.crossPostParams}`}
578 title={i18n.t('cross_post')}
580 <svg class="icon icon-inline">
581 <use xlinkHref="#icon-copy"></use>
587 {this.myPost && this.props.showBody && (
589 <li className="list-inline-item">
591 class="btn btn-sm btn-link btn-animate text-muted"
592 onClick={linkEvent(this, this.handleEditClick)}
593 data-tippy-content={i18n.t('edit')}
595 <svg class="icon icon-inline">
596 <use xlinkHref="#icon-edit"></use>
600 <li className="list-inline-item">
602 class="btn btn-sm btn-link btn-animate text-muted"
603 onClick={linkEvent(this, this.handleDeleteClick)}
611 class={`icon icon-inline ${
612 post.deleted && 'text-danger'
615 <use xlinkHref="#icon-trash"></use>
622 {!this.state.showAdvanced && this.props.showBody ? (
623 <li className="list-inline-item">
625 class="btn btn-sm btn-link btn-animate text-muted"
626 onClick={linkEvent(this, this.handleShowAdvanced)}
627 data-tippy-content={i18n.t('more')}
629 <svg class="icon icon-inline">
630 <use xlinkHref="#icon-more-vertical"></use>
636 {this.props.showBody && post.body && (
637 <li className="list-inline-item">
639 class="btn btn-sm btn-link btn-animate text-muted"
640 onClick={linkEvent(this, this.handleViewSource)}
641 data-tippy-content={i18n.t('view_source')}
644 class={`icon icon-inline ${
645 this.state.viewSource && 'text-success'
648 <use xlinkHref="#icon-file-text"></use>
653 {this.canModOnSelf && (
655 <li className="list-inline-item">
657 class="btn btn-sm btn-link btn-animate text-muted"
658 onClick={linkEvent(this, this.handleModLock)}
666 class={`icon icon-inline ${
667 post.locked && 'text-danger'
670 <use xlinkHref="#icon-lock"></use>
674 <li className="list-inline-item">
676 class="btn btn-sm btn-link btn-animate text-muted"
677 onClick={linkEvent(this, this.handleModSticky)}
685 class={`icon icon-inline ${
686 post.stickied && 'text-success'
689 <use xlinkHref="#icon-pin"></use>
695 {/* Mods can ban from community, and appoint as mods to community */}
696 {(this.canMod || this.canAdmin) && (
697 <li className="list-inline-item">
703 this.handleModRemoveShow
713 this.handleModRemoveSubmit
724 <li className="list-inline-item">
725 {!post.banned_from_community ? (
730 this.handleModBanFromCommunityShow
740 this.handleModBanFromCommunitySubmit
748 {!post.banned_from_community && (
749 <li className="list-inline-item">
754 this.handleAddModToCommunity
758 ? i18n.t('remove_as_mod')
759 : i18n.t('appoint_as_mod')}
765 {/* Community creators and admins can transfer community to another mod */}
766 {(this.amCommunityCreator || this.canAdmin) &&
768 <li className="list-inline-item">
769 {!this.state.showConfirmTransferCommunity ? (
774 this.handleShowConfirmTransferCommunity
777 {i18n.t('transfer_community')}
781 <span class="d-inline-block mr-1">
782 {i18n.t('are_you_sure')}
785 class="pointer d-inline-block mr-1"
788 this.handleTransferCommunity
794 class="pointer d-inline-block"
798 .handleCancelShowConfirmTransferCommunity
807 {/* Admins can ban from all, and appoint other admins */}
811 <li className="list-inline-item">
817 this.handleModBanShow
820 {i18n.t('ban_from_site')}
827 this.handleModBanSubmit
830 {i18n.t('unban_from_site')}
836 <li className="list-inline-item">
839 onClick={linkEvent(this, this.handleAddAdmin)}
842 ? i18n.t('remove_as_admin')
843 : i18n.t('appoint_as_admin')}
849 {/* Site Creator can transfer to another admin */}
850 {this.amSiteCreator && this.isAdmin && (
851 <li className="list-inline-item">
852 {!this.state.showConfirmTransferSite ? (
857 this.handleShowConfirmTransferSite
860 {i18n.t('transfer_site')}
864 <span class="d-inline-block mr-1">
865 {i18n.t('are_you_sure')}
868 class="pointer d-inline-block mr-1"
871 this.handleTransferSite
877 class="pointer d-inline-block"
880 this.handleCancelShowConfirmTransferSite
894 {this.state.showRemoveDialog && (
897 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
901 class="form-control mr-2"
902 placeholder={i18n.t('reason')}
903 value={this.state.removeReason}
904 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
906 <button type="submit" class="btn btn-secondary">
907 {i18n.t('remove_post')}
911 {this.state.showBanDialog && (
912 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
913 <div class="form-group row">
914 <label class="col-form-label" htmlFor="post-listing-reason">
919 id="post-listing-reason"
920 class="form-control mr-2"
921 placeholder={i18n.t('reason')}
922 value={this.state.banReason}
923 onInput={linkEvent(this, this.handleModBanReasonChange)}
926 {/* TODO hold off on expires until later */}
927 {/* <div class="form-group row"> */}
928 {/* <label class="col-form-label">Expires</label> */}
929 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
931 <div class="form-group row">
932 <button type="submit" class="btn btn-secondary">
933 {i18n.t('ban')} {post.creator_name}
945 private get myPost(): boolean {
947 UserService.Instance.user &&
948 this.props.post.creator_id == UserService.Instance.user.id
952 get isMod(): boolean {
954 this.props.moderators &&
956 this.props.moderators.map(m => m.user_id),
957 this.props.post.creator_id
962 get isAdmin(): boolean {
966 this.props.admins.map(a => a.id),
967 this.props.post.creator_id
972 get canMod(): 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
988 get canModOnSelf(): boolean {
989 if (this.props.admins && this.props.moderators) {
990 let adminsThenMods = this.props.admins
992 .concat(this.props.moderators.map(m => m.user_id));
995 UserService.Instance.user,
997 this.props.post.creator_id,
1005 get canAdmin(): boolean {
1007 this.props.admins &&
1009 UserService.Instance.user,
1010 this.props.admins.map(a => a.id),
1011 this.props.post.creator_id
1016 get amCommunityCreator(): boolean {
1018 this.props.moderators &&
1019 UserService.Instance.user &&
1020 this.props.post.creator_id != UserService.Instance.user.id &&
1021 UserService.Instance.user.id == this.props.moderators[0].user_id
1025 get amSiteCreator(): boolean {
1027 this.props.admins &&
1028 UserService.Instance.user &&
1029 this.props.post.creator_id != UserService.Instance.user.id &&
1030 UserService.Instance.user.id == this.props.admins[0].id
1034 handlePostLike(i: PostListing) {
1035 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1037 if (i.state.my_vote == 1) {
1040 } else if (i.state.my_vote == -1) {
1041 i.state.downvotes--;
1049 i.state.my_vote = new_vote;
1051 let form: CreatePostLikeForm = {
1052 post_id: i.props.post.id,
1053 score: i.state.my_vote,
1056 WebSocketService.Instance.likePost(form);
1057 i.setState(i.state);
1061 handlePostDisLike(i: PostListing) {
1062 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1064 if (i.state.my_vote == 1) {
1067 i.state.downvotes++;
1068 } else if (i.state.my_vote == -1) {
1069 i.state.downvotes--;
1072 i.state.downvotes++;
1076 i.state.my_vote = new_vote;
1078 let form: CreatePostLikeForm = {
1079 post_id: i.props.post.id,
1080 score: i.state.my_vote,
1083 WebSocketService.Instance.likePost(form);
1084 i.setState(i.state);
1088 handleEditClick(i: PostListing) {
1089 i.state.showEdit = true;
1090 i.setState(i.state);
1093 handleEditCancel() {
1094 this.state.showEdit = false;
1095 this.setState(this.state);
1098 // The actual editing is done in the recieve for post
1100 this.state.showEdit = false;
1101 this.setState(this.state);
1104 handleDeleteClick(i: PostListing) {
1105 let deleteForm: PostFormI = {
1106 body: i.props.post.body,
1107 community_id: i.props.post.community_id,
1108 name: i.props.post.name,
1109 url: i.props.post.url,
1110 edit_id: i.props.post.id,
1111 creator_id: i.props.post.creator_id,
1112 deleted: !i.props.post.deleted,
1113 nsfw: i.props.post.nsfw,
1116 WebSocketService.Instance.editPost(deleteForm);
1119 handleSavePostClick(i: PostListing) {
1120 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1121 let form: SavePostForm = {
1122 post_id: i.props.post.id,
1126 WebSocketService.Instance.savePost(form);
1129 get crossPostParams(): string {
1130 let params = `?title=${this.props.post.name}`;
1131 let post = this.props.post;
1134 params += `&url=${post.url}`;
1136 if (this.props.post.body) {
1137 params += `&body=${this.props.post.body}`;
1142 handleModRemoveShow(i: PostListing) {
1143 i.state.showRemoveDialog = true;
1144 i.setState(i.state);
1147 handleModRemoveReasonChange(i: PostListing, event: any) {
1148 i.state.removeReason = event.target.value;
1149 i.setState(i.state);
1152 handleModRemoveSubmit(i: PostListing) {
1153 event.preventDefault();
1154 let form: PostFormI = {
1155 name: i.props.post.name,
1156 community_id: i.props.post.community_id,
1157 edit_id: i.props.post.id,
1158 creator_id: i.props.post.creator_id,
1159 removed: !i.props.post.removed,
1160 reason: i.state.removeReason,
1161 nsfw: i.props.post.nsfw,
1164 WebSocketService.Instance.editPost(form);
1166 i.state.showRemoveDialog = false;
1167 i.setState(i.state);
1170 handleModLock(i: PostListing) {
1171 let form: PostFormI = {
1172 name: i.props.post.name,
1173 community_id: i.props.post.community_id,
1174 edit_id: i.props.post.id,
1175 creator_id: i.props.post.creator_id,
1176 nsfw: i.props.post.nsfw,
1177 locked: !i.props.post.locked,
1180 WebSocketService.Instance.editPost(form);
1183 handleModSticky(i: PostListing) {
1184 let form: PostFormI = {
1185 name: i.props.post.name,
1186 community_id: i.props.post.community_id,
1187 edit_id: i.props.post.id,
1188 creator_id: i.props.post.creator_id,
1189 nsfw: i.props.post.nsfw,
1190 stickied: !i.props.post.stickied,
1193 WebSocketService.Instance.editPost(form);
1196 handleModBanFromCommunityShow(i: PostListing) {
1197 i.state.showBanDialog = true;
1198 i.state.banType = BanType.Community;
1199 i.setState(i.state);
1202 handleModBanShow(i: PostListing) {
1203 i.state.showBanDialog = true;
1204 i.state.banType = BanType.Site;
1205 i.setState(i.state);
1208 handleModBanReasonChange(i: PostListing, event: any) {
1209 i.state.banReason = event.target.value;
1210 i.setState(i.state);
1213 handleModBanExpiresChange(i: PostListing, event: any) {
1214 i.state.banExpires = event.target.value;
1215 i.setState(i.state);
1218 handleModBanFromCommunitySubmit(i: PostListing) {
1219 i.state.banType = BanType.Community;
1220 i.setState(i.state);
1221 i.handleModBanBothSubmit(i);
1224 handleModBanSubmit(i: PostListing) {
1225 i.state.banType = BanType.Site;
1226 i.setState(i.state);
1227 i.handleModBanBothSubmit(i);
1230 handleModBanBothSubmit(i: PostListing) {
1231 event.preventDefault();
1233 if (i.state.banType == BanType.Community) {
1234 let form: BanFromCommunityForm = {
1235 user_id: i.props.post.creator_id,
1236 community_id: i.props.post.community_id,
1237 ban: !i.props.post.banned_from_community,
1238 reason: i.state.banReason,
1239 expires: getUnixTime(i.state.banExpires),
1241 WebSocketService.Instance.banFromCommunity(form);
1243 let form: BanUserForm = {
1244 user_id: i.props.post.creator_id,
1245 ban: !i.props.post.banned,
1246 reason: i.state.banReason,
1247 expires: getUnixTime(i.state.banExpires),
1249 WebSocketService.Instance.banUser(form);
1252 i.state.showBanDialog = false;
1253 i.setState(i.state);
1256 handleAddModToCommunity(i: PostListing) {
1257 let form: AddModToCommunityForm = {
1258 user_id: i.props.post.creator_id,
1259 community_id: i.props.post.community_id,
1262 WebSocketService.Instance.addModToCommunity(form);
1263 i.setState(i.state);
1266 handleAddAdmin(i: PostListing) {
1267 let form: AddAdminForm = {
1268 user_id: i.props.post.creator_id,
1271 WebSocketService.Instance.addAdmin(form);
1272 i.setState(i.state);
1275 handleShowConfirmTransferCommunity(i: PostListing) {
1276 i.state.showConfirmTransferCommunity = true;
1277 i.setState(i.state);
1280 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1281 i.state.showConfirmTransferCommunity = false;
1282 i.setState(i.state);
1285 handleTransferCommunity(i: PostListing) {
1286 let form: TransferCommunityForm = {
1287 community_id: i.props.post.community_id,
1288 user_id: i.props.post.creator_id,
1290 WebSocketService.Instance.transferCommunity(form);
1291 i.state.showConfirmTransferCommunity = false;
1292 i.setState(i.state);
1295 handleShowConfirmTransferSite(i: PostListing) {
1296 i.state.showConfirmTransferSite = true;
1297 i.setState(i.state);
1300 handleCancelShowConfirmTransferSite(i: PostListing) {
1301 i.state.showConfirmTransferSite = false;
1302 i.setState(i.state);
1305 handleTransferSite(i: PostListing) {
1306 let form: TransferSiteForm = {
1307 user_id: i.props.post.creator_id,
1309 WebSocketService.Instance.transferSite(form);
1310 i.state.showConfirmTransferSite = false;
1311 i.setState(i.state);
1314 handleImageExpandClick(i: PostListing) {
1315 i.state.imageExpanded = !i.state.imageExpanded;
1316 i.setState(i.state);
1319 handleViewSource(i: PostListing) {
1320 i.state.viewSource = !i.state.viewSource;
1321 i.setState(i.state);
1324 handleShowAdvanced(i: PostListing) {
1325 i.state.showAdvanced = !i.state.showAdvanced;
1326 i.setState(i.state);
1330 get pointsTippy(): string {
1331 let points = i18n.t('number_of_points', {
1332 count: this.state.score,
1335 let upvotes = i18n.t('number_of_upvotes', {
1336 count: this.state.upvotes,
1339 let downvotes = i18n.t('number_of_downvotes', {
1340 count: this.state.downvotes,
1343 return `${points} • ${upvotes} • ${downvotes}`;