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';
30 pictshareAvatarThumbnail,
36 import { i18n } from '../i18next';
38 interface PostListingState {
40 showRemoveDialog: boolean;
42 showBanDialog: boolean;
46 showConfirmTransferSite: boolean;
47 showConfirmTransferCommunity: boolean;
48 imageExpanded: boolean;
50 showAdvanced: boolean;
57 interface PostListingProps {
59 showCommunity?: boolean;
61 moderators?: Array<CommunityUser>;
62 admins?: Array<UserView>;
65 export class PostListing extends Component<PostListingProps, PostListingState> {
66 private emptyState: PostListingState = {
68 showRemoveDialog: false,
73 banType: BanType.Community,
74 showConfirmTransferSite: false,
75 showConfirmTransferCommunity: false,
79 my_vote: this.props.post.my_vote,
80 score: this.props.post.score,
81 upvotes: this.props.post.upvotes,
82 downvotes: this.props.post.downvotes,
85 constructor(props: any, context: any) {
86 super(props, context);
88 this.state = this.emptyState;
89 this.handlePostLike = this.handlePostLike.bind(this);
90 this.handlePostDisLike = this.handlePostDisLike.bind(this);
91 this.handleEditPost = this.handleEditPost.bind(this);
92 this.handleEditCancel = this.handleEditCancel.bind(this);
95 componentWillReceiveProps(nextProps: PostListingProps) {
96 this.state.my_vote = nextProps.post.my_vote;
97 this.state.upvotes = nextProps.post.upvotes;
98 this.state.downvotes = nextProps.post.downvotes;
99 this.state.score = nextProps.post.score;
100 this.setState(this.state);
106 {!this.state.showEdit ? (
114 post={this.props.post}
115 onEdit={this.handleEditPost}
116 onCancel={this.handleEditCancel}
128 {this.props.post.url &&
129 this.props.showBody &&
130 this.props.post.embed_title && (
131 <IFramelyCard post={this.props.post} />
133 {this.props.showBody && this.props.post.body && (
135 {this.state.viewSource ? (
136 <pre>{this.props.post.body}</pre>
140 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
150 imgThumb(src: string) {
151 let post = this.props.post;
154 className={`img-fluid thumbnail rounded ${(post.nsfw ||
155 post.community_nsfw) &&
162 getImage(thumbnail: boolean = false) {
163 let post = this.props.post;
164 if (isImage(post.url)) {
165 if (post.url.includes('pictshare')) {
166 return pictshareImage(post.url, thumbnail);
170 } else if (post.thumbnail_url) {
171 return pictshareImage(post.thumbnail_url, thumbnail);
176 let post = this.props.post;
178 if (isImage(post.url)) {
181 class="text-body pointer"
182 data-tippy-content={i18n.t('expand_here')}
183 onClick={linkEvent(this, this.handleImageExpandClick)}
185 {this.imgThumb(this.getImage(true))}
186 <svg class="icon mini-overlay">
187 <use xlinkHref="#icon-image"></use>
191 } else if (post.thumbnail_url) {
194 className="text-body"
199 {this.imgThumb(this.getImage(true))}
200 <svg class="icon mini-overlay">
201 <use xlinkHref="#icon-external-link"></use>
205 } else if (post.url) {
206 if (isVideo(post.url)) {
208 <div class="embed-responsive embed-responsive-16by9">
214 class="embed-responsive-item"
216 <source src={post.url} type="video/mp4" />
223 className="text-body"
228 <svg class="icon thumbnail">
229 <use xlinkHref="#icon-external-link"></use>
237 className="text-body"
238 to={`/post/${post.id}`}
239 title={i18n.t('comments')}
241 <svg class="icon thumbnail">
242 <use xlinkHref="#icon-message-square"></use>
250 let post = this.props.post;
253 <div className={`vote-bar col-1 pr-0 small text-center`}>
255 className={`btn-animate btn btn-link p-0 ${
256 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
258 onClick={linkEvent(this, this.handlePostLike)}
259 data-tippy-content={i18n.t('upvote')}
261 <svg class="icon upvote">
262 <use xlinkHref="#icon-arrow-up1"></use>
266 class={`unselectable pointer font-weight-bold text-muted px-1`}
267 data-tippy-content={this.pointsTippy}
271 {WebSocketService.Instance.site.enable_downvotes && (
273 className={`btn-animate btn btn-link p-0 ${
274 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
276 onClick={linkEvent(this, this.handlePostDisLike)}
277 data-tippy-content={i18n.t('downvote')}
279 <svg class="icon downvote">
280 <use xlinkHref="#icon-arrow-down1"></use>
285 {!this.state.imageExpanded && (
286 <div class="col-3 col-sm-2 pr-0 mt-1">
287 <div class="position-relative">{this.thumbnail()}</div>
291 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
294 <div className="col-12">
295 <div className="post-title">
296 <h5 className="mb-0 d-inline">
297 {this.props.showBody && post.url ? (
299 className="text-body"
308 className="text-body"
309 to={`/post/${post.id}`}
310 title={i18n.t('comments')}
317 !(new URL(post.url).hostname == window.location.hostname) && (
318 <small class="d-inline-block">
320 className="ml-2 text-muted font-italic"
325 {new URL(post.url).hostname}
326 <svg class="ml-1 icon icon-inline">
327 <use xlinkHref="#icon-external-link"></use>
332 {(isImage(post.url) || this.props.post.thumbnail_url) && (
334 {!this.state.imageExpanded ? (
336 class="text-monospace unselectable pointer ml-2 text-muted small"
337 data-tippy-content={i18n.t('expand_here')}
338 onClick={linkEvent(this, this.handleImageExpandClick)}
340 <svg class="icon icon-inline">
341 <use xlinkHref="#icon-plus-square"></use>
347 class="text-monospace unselectable pointer ml-2 text-muted small"
348 onClick={linkEvent(this, this.handleImageExpandClick)}
350 <svg class="icon icon-inline">
351 <use xlinkHref="#icon-minus-square"></use>
359 this.handleImageExpandClick
363 class="img-fluid img-expanded"
364 src={this.getImage()}
373 <small className="ml-2 text-muted font-italic">
379 className="unselectable pointer ml-2 text-muted font-italic"
380 data-tippy-content={i18n.t('deleted')}
382 <svg class={`icon icon-inline text-danger`}>
383 <use xlinkHref="#icon-trash"></use>
389 className="unselectable pointer ml-2 text-muted font-italic"
390 data-tippy-content={i18n.t('locked')}
392 <svg class={`icon icon-inline text-danger`}>
393 <use xlinkHref="#icon-lock"></use>
399 className="unselectable pointer ml-2 text-muted font-italic"
400 data-tippy-content={i18n.t('stickied')}
402 <svg class={`icon icon-inline text-success`}>
403 <use xlinkHref="#icon-pin"></use>
408 <small className="ml-2 text-muted font-italic">
416 <div className="details col-12">
417 <ul class="list-inline mb-0 text-muted small">
418 <li className="list-inline-item">
419 <span>{i18n.t('by')} </span>
421 className="text-body font-weight-bold"
422 to={`/u/${post.creator_name}`}
424 {post.creator_avatar && showAvatars() && (
428 src={pictshareAvatarThumbnail(post.creator_avatar)}
429 class="rounded-circle mr-1"
432 <span>{post.creator_name}</span>
435 <span className="mx-1 badge badge-light">
440 <span className="mx-1 badge badge-light">
444 {(post.banned_from_community || post.banned) && (
445 <span className="mx-1 badge badge-danger">
449 {this.props.showCommunity && (
451 <span> {i18n.t('to')} </span>
452 <Link to={`/c/${post.community_name}`}>
453 {post.community_name}
458 <li className="list-inline-item">•</li>
459 <li className="list-inline-item">
461 <MomentTime data={post} />
466 <li className="list-inline-item">•</li>
467 <li className="list-inline-item">
468 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
470 className="text-muted"
471 data-tippy-content={md.render(previewLines(post.body))}
472 data-tippy-allowHtml={true}
473 to={`/post/${post.id}`}
475 <svg class="mr-1 icon icon-inline">
476 <use xlinkHref="#icon-book-open"></use>
482 <li className="list-inline-item">•</li>
483 {this.state.upvotes !== this.state.score && (
486 class="unselectable pointer mr-2"
487 data-tippy-content={this.pointsTippy}
489 <li className="list-inline-item">
490 <span className="text-muted">
491 <svg class="small icon icon-inline mr-1">
492 <use xlinkHref="#icon-arrow-up"></use>
497 <li className="list-inline-item">
498 <span className="text-muted">
499 <svg class="small icon icon-inline mr-1">
500 <use xlinkHref="#icon-arrow-down"></use>
502 {this.state.downvotes}
506 <li className="list-inline-item">•</li>
509 <li className="list-inline-item">
511 className="text-muted"
512 title={i18n.t('number_of_comments', {
513 count: post.number_of_comments,
515 to={`/post/${post.id}`}
517 <svg class="mr-1 icon icon-inline">
518 <use xlinkHref="#icon-message-square"></use>
520 {post.number_of_comments}
524 {this.props.post.duplicates && (
525 <ul class="list-inline mb-1 small text-muted">
527 <li className="list-inline-item mr-2">
528 {i18n.t('cross_posted_to')}
530 {this.props.post.duplicates.map(post => (
531 <li className="list-inline-item mr-2">
532 <Link to={`/post/${post.id}`}>
533 {post.community_name}
540 <ul class="list-inline mb-1 text-muted font-weight-bold">
541 {UserService.Instance.user && (
543 {this.props.showBody && (
545 <li className="list-inline-item">
547 class="btn btn-sm btn-link btn-animate text-muted"
548 onClick={linkEvent(this, this.handleSavePostClick)}
550 post.saved ? i18n.t('unsave') : i18n.t('save')
554 class={`icon icon-inline ${post.saved &&
557 <use xlinkHref="#icon-star"></use>
561 <li className="list-inline-item">
563 class="btn btn-sm btn-link btn-animate text-muted"
564 to={`/create_post${this.crossPostParams}`}
565 title={i18n.t('cross_post')}
567 <svg class="icon icon-inline">
568 <use xlinkHref="#icon-copy"></use>
574 {this.myPost && this.props.showBody && (
576 <li className="list-inline-item">
578 class="btn btn-sm btn-link btn-animate text-muted"
579 onClick={linkEvent(this, this.handleEditClick)}
580 data-tippy-content={i18n.t('edit')}
582 <svg class="icon icon-inline">
583 <use xlinkHref="#icon-edit"></use>
587 <li className="list-inline-item">
589 class="btn btn-sm btn-link btn-animate text-muted"
590 onClick={linkEvent(this, this.handleDeleteClick)}
598 class={`icon icon-inline ${post.deleted &&
601 <use xlinkHref="#icon-trash"></use>
608 {!this.state.showAdvanced && this.props.showBody ? (
609 <li className="list-inline-item">
611 class="btn btn-sm btn-link btn-animate text-muted"
612 onClick={linkEvent(this, this.handleShowAdvanced)}
613 data-tippy-content={i18n.t('more')}
615 <svg class="icon icon-inline">
616 <use xlinkHref="#icon-more-vertical"></use>
622 {this.props.showBody && post.body && (
623 <li className="list-inline-item">
625 class="btn btn-sm btn-link btn-animate text-muted"
626 onClick={linkEvent(this, this.handleViewSource)}
627 data-tippy-content={i18n.t('view_source')}
630 class={`icon icon-inline ${this.state
631 .viewSource && 'text-success'}`}
633 <use xlinkHref="#icon-file-text"></use>
638 {this.canModOnSelf && (
640 <li className="list-inline-item">
642 class="btn btn-sm btn-link btn-animate text-muted"
643 onClick={linkEvent(this, this.handleModLock)}
651 class={`icon icon-inline ${post.locked &&
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 ${post.stickied &&
672 <use xlinkHref="#icon-pin"></use>
678 {/* Mods can ban from community, and appoint as mods to community */}
679 {(this.canMod || this.canAdmin) && (
680 <li className="list-inline-item">
686 this.handleModRemoveShow
696 this.handleModRemoveSubmit
707 <li className="list-inline-item">
708 {!post.banned_from_community ? (
713 this.handleModBanFromCommunityShow
723 this.handleModBanFromCommunitySubmit
731 {!post.banned_from_community && (
732 <li className="list-inline-item">
737 this.handleAddModToCommunity
741 ? i18n.t('remove_as_mod')
742 : i18n.t('appoint_as_mod')}
748 {/* Community creators and admins can transfer community to another mod */}
749 {(this.amCommunityCreator || this.canAdmin) &&
751 <li className="list-inline-item">
752 {!this.state.showConfirmTransferCommunity ? (
757 this.handleShowConfirmTransferCommunity
760 {i18n.t('transfer_community')}
764 <span class="d-inline-block mr-1">
765 {i18n.t('are_you_sure')}
768 class="pointer d-inline-block mr-1"
771 this.handleTransferCommunity
777 class="pointer d-inline-block"
781 .handleCancelShowConfirmTransferCommunity
790 {/* Admins can ban from all, and appoint other admins */}
794 <li className="list-inline-item">
800 this.handleModBanShow
803 {i18n.t('ban_from_site')}
810 this.handleModBanSubmit
813 {i18n.t('unban_from_site')}
819 <li className="list-inline-item">
822 onClick={linkEvent(this, this.handleAddAdmin)}
825 ? i18n.t('remove_as_admin')
826 : i18n.t('appoint_as_admin')}
832 {/* Site Creator can transfer to another admin */}
833 {this.amSiteCreator && this.isAdmin && (
834 <li className="list-inline-item">
835 {!this.state.showConfirmTransferSite ? (
840 this.handleShowConfirmTransferSite
843 {i18n.t('transfer_site')}
847 <span class="d-inline-block mr-1">
848 {i18n.t('are_you_sure')}
851 class="pointer d-inline-block mr-1"
854 this.handleTransferSite
860 class="pointer d-inline-block"
863 this.handleCancelShowConfirmTransferSite
877 {this.state.showRemoveDialog && (
880 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
884 class="form-control mr-2"
885 placeholder={i18n.t('reason')}
886 value={this.state.removeReason}
887 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
889 <button type="submit" class="btn btn-secondary">
890 {i18n.t('remove_post')}
894 {this.state.showBanDialog && (
895 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
896 <div class="form-group row">
897 <label class="col-form-label" htmlFor="post-listing-reason">
902 id="post-listing-reason"
903 class="form-control mr-2"
904 placeholder={i18n.t('reason')}
905 value={this.state.banReason}
906 onInput={linkEvent(this, this.handleModBanReasonChange)}
909 {/* TODO hold off on expires until later */}
910 {/* <div class="form-group row"> */}
911 {/* <label class="col-form-label">Expires</label> */}
912 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
914 <div class="form-group row">
915 <button type="submit" class="btn btn-secondary">
916 {i18n.t('ban')} {post.creator_name}
928 private get myPost(): boolean {
930 UserService.Instance.user &&
931 this.props.post.creator_id == UserService.Instance.user.id
935 get isMod(): boolean {
937 this.props.moderators &&
939 this.props.moderators.map(m => m.user_id),
940 this.props.post.creator_id
945 get isAdmin(): boolean {
949 this.props.admins.map(a => a.id),
950 this.props.post.creator_id
955 get canMod(): boolean {
956 if (this.props.admins && this.props.moderators) {
957 let adminsThenMods = this.props.admins
959 .concat(this.props.moderators.map(m => m.user_id));
962 UserService.Instance.user,
964 this.props.post.creator_id
971 get canModOnSelf(): boolean {
972 if (this.props.admins && this.props.moderators) {
973 let adminsThenMods = this.props.admins
975 .concat(this.props.moderators.map(m => m.user_id));
978 UserService.Instance.user,
980 this.props.post.creator_id,
988 get canAdmin(): boolean {
992 UserService.Instance.user,
993 this.props.admins.map(a => a.id),
994 this.props.post.creator_id
999 get amCommunityCreator(): boolean {
1001 this.props.moderators &&
1002 UserService.Instance.user &&
1003 this.props.post.creator_id != UserService.Instance.user.id &&
1004 UserService.Instance.user.id == this.props.moderators[0].user_id
1008 get amSiteCreator(): boolean {
1010 this.props.admins &&
1011 UserService.Instance.user &&
1012 this.props.post.creator_id != UserService.Instance.user.id &&
1013 UserService.Instance.user.id == this.props.admins[0].id
1017 handlePostLike(i: PostListing) {
1018 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1020 if (i.state.my_vote == 1) {
1023 } else if (i.state.my_vote == -1) {
1024 i.state.downvotes--;
1032 i.state.my_vote = new_vote;
1034 let form: CreatePostLikeForm = {
1035 post_id: i.props.post.id,
1036 score: i.state.my_vote,
1039 WebSocketService.Instance.likePost(form);
1040 i.setState(i.state);
1044 handlePostDisLike(i: PostListing) {
1045 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1047 if (i.state.my_vote == 1) {
1050 i.state.downvotes++;
1051 } else if (i.state.my_vote == -1) {
1052 i.state.downvotes--;
1055 i.state.downvotes++;
1059 i.state.my_vote = new_vote;
1061 let form: CreatePostLikeForm = {
1062 post_id: i.props.post.id,
1063 score: i.state.my_vote,
1066 WebSocketService.Instance.likePost(form);
1067 i.setState(i.state);
1071 handleEditClick(i: PostListing) {
1072 i.state.showEdit = true;
1073 i.setState(i.state);
1076 handleEditCancel() {
1077 this.state.showEdit = false;
1078 this.setState(this.state);
1081 // The actual editing is done in the recieve for post
1083 this.state.showEdit = false;
1084 this.setState(this.state);
1087 handleDeleteClick(i: PostListing) {
1088 let deleteForm: PostFormI = {
1089 body: i.props.post.body,
1090 community_id: i.props.post.community_id,
1091 name: i.props.post.name,
1092 url: i.props.post.url,
1093 edit_id: i.props.post.id,
1094 creator_id: i.props.post.creator_id,
1095 deleted: !i.props.post.deleted,
1096 nsfw: i.props.post.nsfw,
1099 WebSocketService.Instance.editPost(deleteForm);
1102 handleSavePostClick(i: PostListing) {
1103 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1104 let form: SavePostForm = {
1105 post_id: i.props.post.id,
1109 WebSocketService.Instance.savePost(form);
1112 get crossPostParams(): string {
1113 let params = `?title=${this.props.post.name}`;
1114 let post = this.props.post;
1117 params += `&url=${post.url}`;
1119 if (this.props.post.body) {
1120 params += `&body=${this.props.post.body}`;
1125 handleModRemoveShow(i: PostListing) {
1126 i.state.showRemoveDialog = true;
1127 i.setState(i.state);
1130 handleModRemoveReasonChange(i: PostListing, event: any) {
1131 i.state.removeReason = event.target.value;
1132 i.setState(i.state);
1135 handleModRemoveSubmit(i: PostListing) {
1136 event.preventDefault();
1137 let form: PostFormI = {
1138 name: i.props.post.name,
1139 community_id: i.props.post.community_id,
1140 edit_id: i.props.post.id,
1141 creator_id: i.props.post.creator_id,
1142 removed: !i.props.post.removed,
1143 reason: i.state.removeReason,
1144 nsfw: i.props.post.nsfw,
1147 WebSocketService.Instance.editPost(form);
1149 i.state.showRemoveDialog = false;
1150 i.setState(i.state);
1153 handleModLock(i: PostListing) {
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 nsfw: i.props.post.nsfw,
1160 locked: !i.props.post.locked,
1163 WebSocketService.Instance.editPost(form);
1166 handleModSticky(i: PostListing) {
1167 let form: PostFormI = {
1168 name: i.props.post.name,
1169 community_id: i.props.post.community_id,
1170 edit_id: i.props.post.id,
1171 creator_id: i.props.post.creator_id,
1172 nsfw: i.props.post.nsfw,
1173 stickied: !i.props.post.stickied,
1176 WebSocketService.Instance.editPost(form);
1179 handleModBanFromCommunityShow(i: PostListing) {
1180 i.state.showBanDialog = true;
1181 i.state.banType = BanType.Community;
1182 i.setState(i.state);
1185 handleModBanShow(i: PostListing) {
1186 i.state.showBanDialog = true;
1187 i.state.banType = BanType.Site;
1188 i.setState(i.state);
1191 handleModBanReasonChange(i: PostListing, event: any) {
1192 i.state.banReason = event.target.value;
1193 i.setState(i.state);
1196 handleModBanExpiresChange(i: PostListing, event: any) {
1197 i.state.banExpires = event.target.value;
1198 i.setState(i.state);
1201 handleModBanFromCommunitySubmit(i: PostListing) {
1202 i.state.banType = BanType.Community;
1203 i.setState(i.state);
1204 i.handleModBanBothSubmit(i);
1207 handleModBanSubmit(i: PostListing) {
1208 i.state.banType = BanType.Site;
1209 i.setState(i.state);
1210 i.handleModBanBothSubmit(i);
1213 handleModBanBothSubmit(i: PostListing) {
1214 event.preventDefault();
1216 if (i.state.banType == BanType.Community) {
1217 let form: BanFromCommunityForm = {
1218 user_id: i.props.post.creator_id,
1219 community_id: i.props.post.community_id,
1220 ban: !i.props.post.banned_from_community,
1221 reason: i.state.banReason,
1222 expires: getUnixTime(i.state.banExpires),
1224 WebSocketService.Instance.banFromCommunity(form);
1226 let form: BanUserForm = {
1227 user_id: i.props.post.creator_id,
1228 ban: !i.props.post.banned,
1229 reason: i.state.banReason,
1230 expires: getUnixTime(i.state.banExpires),
1232 WebSocketService.Instance.banUser(form);
1235 i.state.showBanDialog = false;
1236 i.setState(i.state);
1239 handleAddModToCommunity(i: PostListing) {
1240 let form: AddModToCommunityForm = {
1241 user_id: i.props.post.creator_id,
1242 community_id: i.props.post.community_id,
1245 WebSocketService.Instance.addModToCommunity(form);
1246 i.setState(i.state);
1249 handleAddAdmin(i: PostListing) {
1250 let form: AddAdminForm = {
1251 user_id: i.props.post.creator_id,
1254 WebSocketService.Instance.addAdmin(form);
1255 i.setState(i.state);
1258 handleShowConfirmTransferCommunity(i: PostListing) {
1259 i.state.showConfirmTransferCommunity = true;
1260 i.setState(i.state);
1263 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1264 i.state.showConfirmTransferCommunity = false;
1265 i.setState(i.state);
1268 handleTransferCommunity(i: PostListing) {
1269 let form: TransferCommunityForm = {
1270 community_id: i.props.post.community_id,
1271 user_id: i.props.post.creator_id,
1273 WebSocketService.Instance.transferCommunity(form);
1274 i.state.showConfirmTransferCommunity = false;
1275 i.setState(i.state);
1278 handleShowConfirmTransferSite(i: PostListing) {
1279 i.state.showConfirmTransferSite = true;
1280 i.setState(i.state);
1283 handleCancelShowConfirmTransferSite(i: PostListing) {
1284 i.state.showConfirmTransferSite = false;
1285 i.setState(i.state);
1288 handleTransferSite(i: PostListing) {
1289 let form: TransferSiteForm = {
1290 user_id: i.props.post.creator_id,
1292 WebSocketService.Instance.transferSite(form);
1293 i.state.showConfirmTransferSite = false;
1294 i.setState(i.state);
1297 handleImageExpandClick(i: PostListing) {
1298 i.state.imageExpanded = !i.state.imageExpanded;
1299 i.setState(i.state);
1302 handleViewSource(i: PostListing) {
1303 i.state.viewSource = !i.state.viewSource;
1304 i.setState(i.state);
1307 handleShowAdvanced(i: PostListing) {
1308 i.state.showAdvanced = !i.state.showAdvanced;
1309 i.setState(i.state);
1313 get pointsTippy(): string {
1314 let points = i18n.t('number_of_points', {
1315 count: this.state.score,
1318 let upvotes = i18n.t('number_of_upvotes', {
1319 count: this.state.upvotes,
1322 let downvotes = i18n.t('number_of_downvotes', {
1323 count: this.state.downvotes,
1326 return `${points} • ${upvotes} • ${downvotes}`;