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,
440 <span className="mx-1 badge badge-light">
445 <span className="mx-1 badge badge-light">
449 {(post.banned_from_community || post.banned) && (
450 <span className="mx-1 badge badge-danger">
454 {this.props.showCommunity && (
456 <span> {i18n.t('to')} </span>
459 name: post.community_name,
460 id: post.community_id,
461 local: post.community_local,
462 actor_id: post.community_actor_id,
468 <li className="list-inline-item">•</li>
469 <li className="list-inline-item">
471 <MomentTime data={post} />
476 <li className="list-inline-item">•</li>
477 <li className="list-inline-item">
478 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
480 className="text-muted"
481 data-tippy-content={md.render(previewLines(post.body))}
482 data-tippy-allowHtml={true}
483 to={`/post/${post.id}`}
485 <svg class="mr-1 icon icon-inline">
486 <use xlinkHref="#icon-book-open"></use>
492 <li className="list-inline-item">•</li>
493 {this.state.upvotes !== this.state.score && (
496 class="unselectable pointer mr-2"
497 data-tippy-content={this.pointsTippy}
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-up"></use>
507 <li className="list-inline-item">
508 <span className="text-muted">
509 <svg class="small icon icon-inline mr-1">
510 <use xlinkHref="#icon-arrow-down"></use>
512 {this.state.downvotes}
516 <li className="list-inline-item">•</li>
519 <li className="list-inline-item">
521 className="text-muted"
522 title={i18n.t('number_of_comments', {
523 count: post.number_of_comments,
525 to={`/post/${post.id}`}
527 <svg class="mr-1 icon icon-inline">
528 <use xlinkHref="#icon-message-square"></use>
530 {post.number_of_comments}
534 {this.props.post.duplicates && (
535 <ul class="list-inline mb-1 small text-muted">
537 <li className="list-inline-item mr-2">
538 {i18n.t('cross_posted_to')}
540 {this.props.post.duplicates.map(post => (
541 <li className="list-inline-item mr-2">
542 <Link to={`/post/${post.id}`}>
543 {post.community_name}
550 <ul class="list-inline mb-1 text-muted font-weight-bold">
551 {UserService.Instance.user && (
553 {this.props.showBody && (
555 <li className="list-inline-item">
557 class="btn btn-sm btn-link btn-animate text-muted"
558 onClick={linkEvent(this, this.handleSavePostClick)}
560 post.saved ? i18n.t('unsave') : i18n.t('save')
564 class={`icon icon-inline ${
565 post.saved && 'text-warning'
568 <use xlinkHref="#icon-star"></use>
572 <li className="list-inline-item">
574 class="btn btn-sm btn-link btn-animate text-muted"
575 to={`/create_post${this.crossPostParams}`}
576 title={i18n.t('cross_post')}
578 <svg class="icon icon-inline">
579 <use xlinkHref="#icon-copy"></use>
585 {this.myPost && this.props.showBody && (
587 <li className="list-inline-item">
589 class="btn btn-sm btn-link btn-animate text-muted"
590 onClick={linkEvent(this, this.handleEditClick)}
591 data-tippy-content={i18n.t('edit')}
593 <svg class="icon icon-inline">
594 <use xlinkHref="#icon-edit"></use>
598 <li className="list-inline-item">
600 class="btn btn-sm btn-link btn-animate text-muted"
601 onClick={linkEvent(this, this.handleDeleteClick)}
609 class={`icon icon-inline ${
610 post.deleted && 'text-danger'
613 <use xlinkHref="#icon-trash"></use>
620 {!this.state.showAdvanced && this.props.showBody ? (
621 <li className="list-inline-item">
623 class="btn btn-sm btn-link btn-animate text-muted"
624 onClick={linkEvent(this, this.handleShowAdvanced)}
625 data-tippy-content={i18n.t('more')}
627 <svg class="icon icon-inline">
628 <use xlinkHref="#icon-more-vertical"></use>
634 {this.props.showBody && post.body && (
635 <li className="list-inline-item">
637 class="btn btn-sm btn-link btn-animate text-muted"
638 onClick={linkEvent(this, this.handleViewSource)}
639 data-tippy-content={i18n.t('view_source')}
642 class={`icon icon-inline ${
643 this.state.viewSource && 'text-success'
646 <use xlinkHref="#icon-file-text"></use>
651 {this.canModOnSelf && (
653 <li className="list-inline-item">
655 class="btn btn-sm btn-link btn-animate text-muted"
656 onClick={linkEvent(this, this.handleModLock)}
664 class={`icon icon-inline ${
665 post.locked && 'text-danger'
668 <use xlinkHref="#icon-lock"></use>
672 <li className="list-inline-item">
674 class="btn btn-sm btn-link btn-animate text-muted"
675 onClick={linkEvent(this, this.handleModSticky)}
683 class={`icon icon-inline ${
684 post.stickied && 'text-success'
687 <use xlinkHref="#icon-pin"></use>
693 {/* Mods can ban from community, and appoint as mods to community */}
694 {(this.canMod || this.canAdmin) && (
695 <li className="list-inline-item">
701 this.handleModRemoveShow
711 this.handleModRemoveSubmit
722 <li className="list-inline-item">
723 {!post.banned_from_community ? (
728 this.handleModBanFromCommunityShow
738 this.handleModBanFromCommunitySubmit
746 {!post.banned_from_community && (
747 <li className="list-inline-item">
752 this.handleAddModToCommunity
756 ? i18n.t('remove_as_mod')
757 : i18n.t('appoint_as_mod')}
763 {/* Community creators and admins can transfer community to another mod */}
764 {(this.amCommunityCreator || this.canAdmin) &&
766 <li className="list-inline-item">
767 {!this.state.showConfirmTransferCommunity ? (
772 this.handleShowConfirmTransferCommunity
775 {i18n.t('transfer_community')}
779 <span class="d-inline-block mr-1">
780 {i18n.t('are_you_sure')}
783 class="pointer d-inline-block mr-1"
786 this.handleTransferCommunity
792 class="pointer d-inline-block"
796 .handleCancelShowConfirmTransferCommunity
805 {/* Admins can ban from all, and appoint other admins */}
809 <li className="list-inline-item">
815 this.handleModBanShow
818 {i18n.t('ban_from_site')}
825 this.handleModBanSubmit
828 {i18n.t('unban_from_site')}
834 <li className="list-inline-item">
837 onClick={linkEvent(this, this.handleAddAdmin)}
840 ? i18n.t('remove_as_admin')
841 : i18n.t('appoint_as_admin')}
847 {/* Site Creator can transfer to another admin */}
848 {this.amSiteCreator && this.isAdmin && (
849 <li className="list-inline-item">
850 {!this.state.showConfirmTransferSite ? (
855 this.handleShowConfirmTransferSite
858 {i18n.t('transfer_site')}
862 <span class="d-inline-block mr-1">
863 {i18n.t('are_you_sure')}
866 class="pointer d-inline-block mr-1"
869 this.handleTransferSite
875 class="pointer d-inline-block"
878 this.handleCancelShowConfirmTransferSite
892 {this.state.showRemoveDialog && (
895 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
899 class="form-control mr-2"
900 placeholder={i18n.t('reason')}
901 value={this.state.removeReason}
902 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
904 <button type="submit" class="btn btn-secondary">
905 {i18n.t('remove_post')}
909 {this.state.showBanDialog && (
910 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
911 <div class="form-group row">
912 <label class="col-form-label" htmlFor="post-listing-reason">
917 id="post-listing-reason"
918 class="form-control mr-2"
919 placeholder={i18n.t('reason')}
920 value={this.state.banReason}
921 onInput={linkEvent(this, this.handleModBanReasonChange)}
924 {/* TODO hold off on expires until later */}
925 {/* <div class="form-group row"> */}
926 {/* <label class="col-form-label">Expires</label> */}
927 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
929 <div class="form-group row">
930 <button type="submit" class="btn btn-secondary">
931 {i18n.t('ban')} {post.creator_name}
943 private get myPost(): boolean {
945 UserService.Instance.user &&
946 this.props.post.creator_id == UserService.Instance.user.id
950 get isMod(): boolean {
952 this.props.moderators &&
954 this.props.moderators.map(m => m.user_id),
955 this.props.post.creator_id
960 get isAdmin(): boolean {
964 this.props.admins.map(a => a.id),
965 this.props.post.creator_id
970 get canMod(): boolean {
971 if (this.props.admins && this.props.moderators) {
972 let adminsThenMods = this.props.admins
974 .concat(this.props.moderators.map(m => m.user_id));
977 UserService.Instance.user,
979 this.props.post.creator_id
986 get canModOnSelf(): boolean {
987 if (this.props.admins && this.props.moderators) {
988 let adminsThenMods = this.props.admins
990 .concat(this.props.moderators.map(m => m.user_id));
993 UserService.Instance.user,
995 this.props.post.creator_id,
1003 get canAdmin(): boolean {
1005 this.props.admins &&
1007 UserService.Instance.user,
1008 this.props.admins.map(a => a.id),
1009 this.props.post.creator_id
1014 get amCommunityCreator(): boolean {
1016 this.props.moderators &&
1017 UserService.Instance.user &&
1018 this.props.post.creator_id != UserService.Instance.user.id &&
1019 UserService.Instance.user.id == this.props.moderators[0].user_id
1023 get amSiteCreator(): boolean {
1025 this.props.admins &&
1026 UserService.Instance.user &&
1027 this.props.post.creator_id != UserService.Instance.user.id &&
1028 UserService.Instance.user.id == this.props.admins[0].id
1032 handlePostLike(i: PostListing) {
1033 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1035 if (i.state.my_vote == 1) {
1038 } else if (i.state.my_vote == -1) {
1039 i.state.downvotes--;
1047 i.state.my_vote = new_vote;
1049 let form: CreatePostLikeForm = {
1050 post_id: i.props.post.id,
1051 score: i.state.my_vote,
1054 WebSocketService.Instance.likePost(form);
1055 i.setState(i.state);
1059 handlePostDisLike(i: PostListing) {
1060 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1062 if (i.state.my_vote == 1) {
1065 i.state.downvotes++;
1066 } else if (i.state.my_vote == -1) {
1067 i.state.downvotes--;
1070 i.state.downvotes++;
1074 i.state.my_vote = new_vote;
1076 let form: CreatePostLikeForm = {
1077 post_id: i.props.post.id,
1078 score: i.state.my_vote,
1081 WebSocketService.Instance.likePost(form);
1082 i.setState(i.state);
1086 handleEditClick(i: PostListing) {
1087 i.state.showEdit = true;
1088 i.setState(i.state);
1091 handleEditCancel() {
1092 this.state.showEdit = false;
1093 this.setState(this.state);
1096 // The actual editing is done in the recieve for post
1098 this.state.showEdit = false;
1099 this.setState(this.state);
1102 handleDeleteClick(i: PostListing) {
1103 let deleteForm: PostFormI = {
1104 body: i.props.post.body,
1105 community_id: i.props.post.community_id,
1106 name: i.props.post.name,
1107 url: i.props.post.url,
1108 edit_id: i.props.post.id,
1109 creator_id: i.props.post.creator_id,
1110 deleted: !i.props.post.deleted,
1111 nsfw: i.props.post.nsfw,
1114 WebSocketService.Instance.editPost(deleteForm);
1117 handleSavePostClick(i: PostListing) {
1118 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1119 let form: SavePostForm = {
1120 post_id: i.props.post.id,
1124 WebSocketService.Instance.savePost(form);
1127 get crossPostParams(): string {
1128 let params = `?title=${this.props.post.name}`;
1129 let post = this.props.post;
1132 params += `&url=${post.url}`;
1134 if (this.props.post.body) {
1135 params += `&body=${this.props.post.body}`;
1140 handleModRemoveShow(i: PostListing) {
1141 i.state.showRemoveDialog = true;
1142 i.setState(i.state);
1145 handleModRemoveReasonChange(i: PostListing, event: any) {
1146 i.state.removeReason = event.target.value;
1147 i.setState(i.state);
1150 handleModRemoveSubmit(i: PostListing) {
1151 event.preventDefault();
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 removed: !i.props.post.removed,
1158 reason: i.state.removeReason,
1159 nsfw: i.props.post.nsfw,
1162 WebSocketService.Instance.editPost(form);
1164 i.state.showRemoveDialog = false;
1165 i.setState(i.state);
1168 handleModLock(i: PostListing) {
1169 let form: PostFormI = {
1170 name: i.props.post.name,
1171 community_id: i.props.post.community_id,
1172 edit_id: i.props.post.id,
1173 creator_id: i.props.post.creator_id,
1174 nsfw: i.props.post.nsfw,
1175 locked: !i.props.post.locked,
1178 WebSocketService.Instance.editPost(form);
1181 handleModSticky(i: PostListing) {
1182 let form: PostFormI = {
1183 name: i.props.post.name,
1184 community_id: i.props.post.community_id,
1185 edit_id: i.props.post.id,
1186 creator_id: i.props.post.creator_id,
1187 nsfw: i.props.post.nsfw,
1188 stickied: !i.props.post.stickied,
1191 WebSocketService.Instance.editPost(form);
1194 handleModBanFromCommunityShow(i: PostListing) {
1195 i.state.showBanDialog = true;
1196 i.state.banType = BanType.Community;
1197 i.setState(i.state);
1200 handleModBanShow(i: PostListing) {
1201 i.state.showBanDialog = true;
1202 i.state.banType = BanType.Site;
1203 i.setState(i.state);
1206 handleModBanReasonChange(i: PostListing, event: any) {
1207 i.state.banReason = event.target.value;
1208 i.setState(i.state);
1211 handleModBanExpiresChange(i: PostListing, event: any) {
1212 i.state.banExpires = event.target.value;
1213 i.setState(i.state);
1216 handleModBanFromCommunitySubmit(i: PostListing) {
1217 i.state.banType = BanType.Community;
1218 i.setState(i.state);
1219 i.handleModBanBothSubmit(i);
1222 handleModBanSubmit(i: PostListing) {
1223 i.state.banType = BanType.Site;
1224 i.setState(i.state);
1225 i.handleModBanBothSubmit(i);
1228 handleModBanBothSubmit(i: PostListing) {
1229 event.preventDefault();
1231 if (i.state.banType == BanType.Community) {
1232 let form: BanFromCommunityForm = {
1233 user_id: i.props.post.creator_id,
1234 community_id: i.props.post.community_id,
1235 ban: !i.props.post.banned_from_community,
1236 reason: i.state.banReason,
1237 expires: getUnixTime(i.state.banExpires),
1239 WebSocketService.Instance.banFromCommunity(form);
1241 let form: BanUserForm = {
1242 user_id: i.props.post.creator_id,
1243 ban: !i.props.post.banned,
1244 reason: i.state.banReason,
1245 expires: getUnixTime(i.state.banExpires),
1247 WebSocketService.Instance.banUser(form);
1250 i.state.showBanDialog = false;
1251 i.setState(i.state);
1254 handleAddModToCommunity(i: PostListing) {
1255 let form: AddModToCommunityForm = {
1256 user_id: i.props.post.creator_id,
1257 community_id: i.props.post.community_id,
1260 WebSocketService.Instance.addModToCommunity(form);
1261 i.setState(i.state);
1264 handleAddAdmin(i: PostListing) {
1265 let form: AddAdminForm = {
1266 user_id: i.props.post.creator_id,
1269 WebSocketService.Instance.addAdmin(form);
1270 i.setState(i.state);
1273 handleShowConfirmTransferCommunity(i: PostListing) {
1274 i.state.showConfirmTransferCommunity = true;
1275 i.setState(i.state);
1278 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1279 i.state.showConfirmTransferCommunity = false;
1280 i.setState(i.state);
1283 handleTransferCommunity(i: PostListing) {
1284 let form: TransferCommunityForm = {
1285 community_id: i.props.post.community_id,
1286 user_id: i.props.post.creator_id,
1288 WebSocketService.Instance.transferCommunity(form);
1289 i.state.showConfirmTransferCommunity = false;
1290 i.setState(i.state);
1293 handleShowConfirmTransferSite(i: PostListing) {
1294 i.state.showConfirmTransferSite = true;
1295 i.setState(i.state);
1298 handleCancelShowConfirmTransferSite(i: PostListing) {
1299 i.state.showConfirmTransferSite = false;
1300 i.setState(i.state);
1303 handleTransferSite(i: PostListing) {
1304 let form: TransferSiteForm = {
1305 user_id: i.props.post.creator_id,
1307 WebSocketService.Instance.transferSite(form);
1308 i.state.showConfirmTransferSite = false;
1309 i.setState(i.state);
1312 handleImageExpandClick(i: PostListing) {
1313 i.state.imageExpanded = !i.state.imageExpanded;
1314 i.setState(i.state);
1317 handleViewSource(i: PostListing) {
1318 i.state.viewSource = !i.state.viewSource;
1319 i.setState(i.state);
1322 handleShowAdvanced(i: PostListing) {
1323 i.state.showAdvanced = !i.state.showAdvanced;
1324 i.setState(i.state);
1328 get pointsTippy(): string {
1329 let points = i18n.t('number_of_points', {
1330 count: this.state.score,
1333 let upvotes = i18n.t('number_of_upvotes', {
1334 count: this.state.upvotes,
1337 let downvotes = i18n.t('number_of_downvotes', {
1338 count: this.state.downvotes,
1341 return `${points} • ${upvotes} • ${downvotes}`;