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';
38 import { CakeDay } from './cake-day';
40 interface PostListingState {
42 showRemoveDialog: boolean;
44 showBanDialog: boolean;
48 showConfirmTransferSite: boolean;
49 showConfirmTransferCommunity: boolean;
50 imageExpanded: boolean;
52 showAdvanced: boolean;
59 interface PostListingProps {
61 showCommunity?: boolean;
63 moderators?: Array<CommunityUser>;
64 admins?: Array<UserView>;
67 export class PostListing extends Component<PostListingProps, PostListingState> {
68 private emptyState: PostListingState = {
70 showRemoveDialog: false,
75 banType: BanType.Community,
76 showConfirmTransferSite: false,
77 showConfirmTransferCommunity: false,
81 my_vote: this.props.post.my_vote,
82 score: this.props.post.score,
83 upvotes: this.props.post.upvotes,
84 downvotes: this.props.post.downvotes,
87 constructor(props: any, context: any) {
88 super(props, context);
90 this.state = this.emptyState;
91 this.handlePostLike = this.handlePostLike.bind(this);
92 this.handlePostDisLike = this.handlePostDisLike.bind(this);
93 this.handleEditPost = this.handleEditPost.bind(this);
94 this.handleEditCancel = this.handleEditCancel.bind(this);
97 componentWillReceiveProps(nextProps: PostListingProps) {
98 this.state.my_vote = nextProps.post.my_vote;
99 this.state.upvotes = nextProps.post.upvotes;
100 this.state.downvotes = nextProps.post.downvotes;
101 this.state.score = nextProps.post.score;
102 this.setState(this.state);
108 {!this.state.showEdit ? (
116 post={this.props.post}
117 onEdit={this.handleEditPost}
118 onCancel={this.handleEditCancel}
130 {this.props.post.url &&
131 this.props.showBody &&
132 this.props.post.embed_title && (
133 <IFramelyCard post={this.props.post} />
135 {this.props.showBody && this.props.post.body && (
137 {this.state.viewSource ? (
138 <pre>{this.props.post.body}</pre>
142 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
152 imgThumb(src: string) {
153 let post = this.props.post;
156 className={`img-fluid thumbnail rounded ${
157 (post.nsfw || post.community_nsfw) && 'img-blur'
164 getImage(thumbnail: boolean = false) {
165 let post = this.props.post;
166 if (isImage(post.url)) {
167 if (post.url.includes('pictrs')) {
168 return pictrsImage(post.url, thumbnail);
169 } else if (post.thumbnail_url) {
170 return pictrsImage(post.thumbnail_url, thumbnail);
174 } else if (post.thumbnail_url) {
175 return pictrsImage(post.thumbnail_url, thumbnail);
180 let post = this.props.post;
182 if (isImage(post.url)) {
185 class="text-body pointer"
186 data-tippy-content={i18n.t('expand_here')}
187 onClick={linkEvent(this, this.handleImageExpandClick)}
189 {this.imgThumb(this.getImage(true))}
190 <svg class="icon mini-overlay">
191 <use xlinkHref="#icon-image"></use>
195 } else if (post.thumbnail_url) {
198 className="text-body"
204 {this.imgThumb(this.getImage(true))}
205 <svg class="icon mini-overlay">
206 <use xlinkHref="#icon-external-link"></use>
210 } else if (post.url) {
211 if (isVideo(post.url)) {
213 <div class="embed-responsive embed-responsive-16by9">
219 class="embed-responsive-item"
221 <source src={post.url} type="video/mp4" />
228 className="text-body"
234 <svg class="icon thumbnail">
235 <use xlinkHref="#icon-external-link"></use>
243 className="text-body"
244 to={`/post/${post.id}`}
245 title={i18n.t('comments')}
247 <svg class="icon thumbnail">
248 <use xlinkHref="#icon-message-square"></use>
256 let post = this.props.post;
257 const { creator_name, creator_published } = post;
261 <div className={`vote-bar col-1 pr-0 small text-center`}>
263 className={`btn-animate btn btn-link p-0 ${
264 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
266 onClick={linkEvent(this, this.handlePostLike)}
267 data-tippy-content={i18n.t('upvote')}
269 <svg class="icon upvote">
270 <use xlinkHref="#icon-arrow-up1"></use>
274 class={`unselectable pointer font-weight-bold text-muted px-1`}
275 data-tippy-content={this.pointsTippy}
279 {WebSocketService.Instance.site.enable_downvotes && (
281 className={`btn-animate btn btn-link p-0 ${
282 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
284 onClick={linkEvent(this, this.handlePostDisLike)}
285 data-tippy-content={i18n.t('downvote')}
287 <svg class="icon downvote">
288 <use xlinkHref="#icon-arrow-down1"></use>
293 {!this.state.imageExpanded && (
294 <div class="col-3 col-sm-2 pr-0 mt-1">
295 <div class="position-relative">{this.thumbnail()}</div>
299 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
302 <div className="col-12">
303 <div className="post-title">
304 <h5 className="mb-0 d-inline">
305 {this.props.showBody && post.url ? (
307 className="text-body"
317 className="text-body"
318 to={`/post/${post.id}`}
319 title={i18n.t('comments')}
325 {post.url && !(hostname(post.url) == window.location.hostname) && (
326 <small class="d-inline-block">
328 className="ml-2 text-muted font-italic"
335 <svg class="ml-1 icon icon-inline">
336 <use xlinkHref="#icon-external-link"></use>
341 {(isImage(post.url) || this.props.post.thumbnail_url) && (
343 {!this.state.imageExpanded ? (
345 class="text-monospace unselectable pointer ml-2 text-muted small"
346 data-tippy-content={i18n.t('expand_here')}
347 onClick={linkEvent(this, this.handleImageExpandClick)}
349 <svg class="icon icon-inline">
350 <use xlinkHref="#icon-plus-square"></use>
356 class="text-monospace unselectable pointer ml-2 text-muted small"
357 onClick={linkEvent(this, this.handleImageExpandClick)}
359 <svg class="icon icon-inline">
360 <use xlinkHref="#icon-minus-square"></use>
368 this.handleImageExpandClick
372 class="img-fluid img-expanded"
373 src={this.getImage()}
382 <small className="ml-2 text-muted font-italic">
388 className="unselectable pointer ml-2 text-muted font-italic"
389 data-tippy-content={i18n.t('deleted')}
391 <svg class={`icon icon-inline text-danger`}>
392 <use xlinkHref="#icon-trash"></use>
398 className="unselectable pointer ml-2 text-muted font-italic"
399 data-tippy-content={i18n.t('locked')}
401 <svg class={`icon icon-inline text-danger`}>
402 <use xlinkHref="#icon-lock"></use>
408 className="unselectable pointer ml-2 text-muted font-italic"
409 data-tippy-content={i18n.t('stickied')}
411 <svg class={`icon icon-inline text-success`}>
412 <use xlinkHref="#icon-pin"></use>
417 <small className="ml-2 text-muted font-italic">
425 <div className="details col-12">
426 <ul class="list-inline mb-0 text-muted small">
427 <li className="list-inline-item">
428 <span>{i18n.t('by')} </span>
431 name: post.creator_name,
432 avatar: post.creator_avatar,
434 local: post.creator_local,
435 actor_id: post.creator_actor_id,
440 creator_name={creator_name}
441 creator_published={creator_published}
445 <span className="mx-1 badge badge-light">
450 <span className="mx-1 badge badge-light">
454 {(post.banned_from_community || post.banned) && (
455 <span className="mx-1 badge badge-danger">
459 {this.props.showCommunity && (
461 <span> {i18n.t('to')} </span>
464 name: post.community_name,
465 id: post.community_id,
466 local: post.community_local,
467 actor_id: post.community_actor_id,
473 <li className="list-inline-item">•</li>
474 <li className="list-inline-item">
476 <MomentTime data={post} />
481 <li className="list-inline-item">•</li>
482 <li className="list-inline-item">
483 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
485 className="text-muted"
486 data-tippy-content={md.render(previewLines(post.body))}
487 data-tippy-allowHtml={true}
488 to={`/post/${post.id}`}
490 <svg class="mr-1 icon icon-inline">
491 <use xlinkHref="#icon-book-open"></use>
497 <li className="list-inline-item">•</li>
498 {this.state.upvotes !== this.state.score && (
501 class="unselectable pointer mr-2"
502 data-tippy-content={this.pointsTippy}
504 <li className="list-inline-item">
505 <span className="text-muted">
506 <svg class="small icon icon-inline mr-1">
507 <use xlinkHref="#icon-arrow-up"></use>
512 <li className="list-inline-item">
513 <span className="text-muted">
514 <svg class="small icon icon-inline mr-1">
515 <use xlinkHref="#icon-arrow-down"></use>
517 {this.state.downvotes}
521 <li className="list-inline-item">•</li>
524 <li className="list-inline-item">
526 className="text-muted"
527 title={i18n.t('number_of_comments', {
528 count: post.number_of_comments,
530 to={`/post/${post.id}`}
532 <svg class="mr-1 icon icon-inline">
533 <use xlinkHref="#icon-message-square"></use>
535 {post.number_of_comments}
539 {this.props.post.duplicates && (
540 <ul class="list-inline mb-1 small text-muted">
542 <li className="list-inline-item mr-2">
543 {i18n.t('cross_posted_to')}
545 {this.props.post.duplicates.map(post => (
546 <li className="list-inline-item mr-2">
547 <Link to={`/post/${post.id}`}>
548 {post.community_name}
555 <ul class="list-inline mb-1 text-muted font-weight-bold">
556 {UserService.Instance.user && (
558 {this.props.showBody && (
560 <li className="list-inline-item">
562 class="btn btn-sm btn-link btn-animate text-muted"
563 onClick={linkEvent(this, this.handleSavePostClick)}
565 post.saved ? i18n.t('unsave') : i18n.t('save')
569 class={`icon icon-inline ${
570 post.saved && 'text-warning'
573 <use xlinkHref="#icon-star"></use>
577 <li className="list-inline-item">
579 class="btn btn-sm btn-link btn-animate text-muted"
580 to={`/create_post${this.crossPostParams}`}
581 title={i18n.t('cross_post')}
583 <svg class="icon icon-inline">
584 <use xlinkHref="#icon-copy"></use>
590 {this.myPost && this.props.showBody && (
592 <li className="list-inline-item">
594 class="btn btn-sm btn-link btn-animate text-muted"
595 onClick={linkEvent(this, this.handleEditClick)}
596 data-tippy-content={i18n.t('edit')}
598 <svg class="icon icon-inline">
599 <use xlinkHref="#icon-edit"></use>
603 <li className="list-inline-item">
605 class="btn btn-sm btn-link btn-animate text-muted"
606 onClick={linkEvent(this, this.handleDeleteClick)}
614 class={`icon icon-inline ${
615 post.deleted && 'text-danger'
618 <use xlinkHref="#icon-trash"></use>
625 {!this.state.showAdvanced && this.props.showBody ? (
626 <li className="list-inline-item">
628 class="btn btn-sm btn-link btn-animate text-muted"
629 onClick={linkEvent(this, this.handleShowAdvanced)}
630 data-tippy-content={i18n.t('more')}
632 <svg class="icon icon-inline">
633 <use xlinkHref="#icon-more-vertical"></use>
639 {this.props.showBody && post.body && (
640 <li className="list-inline-item">
642 class="btn btn-sm btn-link btn-animate text-muted"
643 onClick={linkEvent(this, this.handleViewSource)}
644 data-tippy-content={i18n.t('view_source')}
647 class={`icon icon-inline ${
648 this.state.viewSource && 'text-success'
651 <use xlinkHref="#icon-file-text"></use>
656 {this.canModOnSelf && (
658 <li className="list-inline-item">
660 class="btn btn-sm btn-link btn-animate text-muted"
661 onClick={linkEvent(this, this.handleModLock)}
669 class={`icon icon-inline ${
670 post.locked && 'text-danger'
673 <use xlinkHref="#icon-lock"></use>
677 <li className="list-inline-item">
679 class="btn btn-sm btn-link btn-animate text-muted"
680 onClick={linkEvent(this, this.handleModSticky)}
688 class={`icon icon-inline ${
689 post.stickied && 'text-success'
692 <use xlinkHref="#icon-pin"></use>
698 {/* Mods can ban from community, and appoint as mods to community */}
699 {(this.canMod || this.canAdmin) && (
700 <li className="list-inline-item">
706 this.handleModRemoveShow
716 this.handleModRemoveSubmit
727 <li className="list-inline-item">
728 {!post.banned_from_community ? (
733 this.handleModBanFromCommunityShow
743 this.handleModBanFromCommunitySubmit
751 {!post.banned_from_community && (
752 <li className="list-inline-item">
757 this.handleAddModToCommunity
761 ? i18n.t('remove_as_mod')
762 : i18n.t('appoint_as_mod')}
768 {/* Community creators and admins can transfer community to another mod */}
769 {(this.amCommunityCreator || this.canAdmin) &&
771 <li className="list-inline-item">
772 {!this.state.showConfirmTransferCommunity ? (
777 this.handleShowConfirmTransferCommunity
780 {i18n.t('transfer_community')}
784 <span class="d-inline-block mr-1">
785 {i18n.t('are_you_sure')}
788 class="pointer d-inline-block mr-1"
791 this.handleTransferCommunity
797 class="pointer d-inline-block"
801 .handleCancelShowConfirmTransferCommunity
810 {/* Admins can ban from all, and appoint other admins */}
814 <li className="list-inline-item">
820 this.handleModBanShow
823 {i18n.t('ban_from_site')}
830 this.handleModBanSubmit
833 {i18n.t('unban_from_site')}
839 <li className="list-inline-item">
842 onClick={linkEvent(this, this.handleAddAdmin)}
845 ? i18n.t('remove_as_admin')
846 : i18n.t('appoint_as_admin')}
852 {/* Site Creator can transfer to another admin */}
853 {this.amSiteCreator && this.isAdmin && (
854 <li className="list-inline-item">
855 {!this.state.showConfirmTransferSite ? (
860 this.handleShowConfirmTransferSite
863 {i18n.t('transfer_site')}
867 <span class="d-inline-block mr-1">
868 {i18n.t('are_you_sure')}
871 class="pointer d-inline-block mr-1"
874 this.handleTransferSite
880 class="pointer d-inline-block"
883 this.handleCancelShowConfirmTransferSite
897 {this.state.showRemoveDialog && (
900 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
904 class="form-control mr-2"
905 placeholder={i18n.t('reason')}
906 value={this.state.removeReason}
907 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
909 <button type="submit" class="btn btn-secondary">
910 {i18n.t('remove_post')}
914 {this.state.showBanDialog && (
915 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
916 <div class="form-group row">
917 <label class="col-form-label" htmlFor="post-listing-reason">
922 id="post-listing-reason"
923 class="form-control mr-2"
924 placeholder={i18n.t('reason')}
925 value={this.state.banReason}
926 onInput={linkEvent(this, this.handleModBanReasonChange)}
929 {/* TODO hold off on expires until later */}
930 {/* <div class="form-group row"> */}
931 {/* <label class="col-form-label">Expires</label> */}
932 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
934 <div class="form-group row">
935 <button type="submit" class="btn btn-secondary">
936 {i18n.t('ban')} {post.creator_name}
948 private get myPost(): boolean {
950 UserService.Instance.user &&
951 this.props.post.creator_id == UserService.Instance.user.id
955 get isMod(): boolean {
957 this.props.moderators &&
959 this.props.moderators.map(m => m.user_id),
960 this.props.post.creator_id
965 get isAdmin(): boolean {
969 this.props.admins.map(a => a.id),
970 this.props.post.creator_id
975 get canMod(): boolean {
976 if (this.props.admins && this.props.moderators) {
977 let adminsThenMods = this.props.admins
979 .concat(this.props.moderators.map(m => m.user_id));
982 UserService.Instance.user,
984 this.props.post.creator_id
991 get canModOnSelf(): boolean {
992 if (this.props.admins && this.props.moderators) {
993 let adminsThenMods = this.props.admins
995 .concat(this.props.moderators.map(m => m.user_id));
998 UserService.Instance.user,
1000 this.props.post.creator_id,
1008 get canAdmin(): boolean {
1010 this.props.admins &&
1012 UserService.Instance.user,
1013 this.props.admins.map(a => a.id),
1014 this.props.post.creator_id
1019 get amCommunityCreator(): boolean {
1021 this.props.moderators &&
1022 UserService.Instance.user &&
1023 this.props.post.creator_id != UserService.Instance.user.id &&
1024 UserService.Instance.user.id == this.props.moderators[0].user_id
1028 get amSiteCreator(): boolean {
1030 this.props.admins &&
1031 UserService.Instance.user &&
1032 this.props.post.creator_id != UserService.Instance.user.id &&
1033 UserService.Instance.user.id == this.props.admins[0].id
1037 handlePostLike(i: PostListing) {
1038 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1040 if (i.state.my_vote == 1) {
1043 } else if (i.state.my_vote == -1) {
1044 i.state.downvotes--;
1052 i.state.my_vote = new_vote;
1054 let form: CreatePostLikeForm = {
1055 post_id: i.props.post.id,
1056 score: i.state.my_vote,
1059 WebSocketService.Instance.likePost(form);
1060 i.setState(i.state);
1064 handlePostDisLike(i: PostListing) {
1065 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1067 if (i.state.my_vote == 1) {
1070 i.state.downvotes++;
1071 } else if (i.state.my_vote == -1) {
1072 i.state.downvotes--;
1075 i.state.downvotes++;
1079 i.state.my_vote = new_vote;
1081 let form: CreatePostLikeForm = {
1082 post_id: i.props.post.id,
1083 score: i.state.my_vote,
1086 WebSocketService.Instance.likePost(form);
1087 i.setState(i.state);
1091 handleEditClick(i: PostListing) {
1092 i.state.showEdit = true;
1093 i.setState(i.state);
1096 handleEditCancel() {
1097 this.state.showEdit = false;
1098 this.setState(this.state);
1101 // The actual editing is done in the recieve for post
1103 this.state.showEdit = false;
1104 this.setState(this.state);
1107 handleDeleteClick(i: PostListing) {
1108 let deleteForm: PostFormI = {
1109 body: i.props.post.body,
1110 community_id: i.props.post.community_id,
1111 name: i.props.post.name,
1112 url: i.props.post.url,
1113 edit_id: i.props.post.id,
1114 creator_id: i.props.post.creator_id,
1115 deleted: !i.props.post.deleted,
1116 nsfw: i.props.post.nsfw,
1119 WebSocketService.Instance.editPost(deleteForm);
1122 handleSavePostClick(i: PostListing) {
1123 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1124 let form: SavePostForm = {
1125 post_id: i.props.post.id,
1129 WebSocketService.Instance.savePost(form);
1132 get crossPostParams(): string {
1133 let params = `?title=${this.props.post.name}`;
1134 let post = this.props.post;
1137 params += `&url=${post.url}`;
1139 if (this.props.post.body) {
1140 params += `&body=${this.props.post.body}`;
1145 handleModRemoveShow(i: PostListing) {
1146 i.state.showRemoveDialog = true;
1147 i.setState(i.state);
1150 handleModRemoveReasonChange(i: PostListing, event: any) {
1151 i.state.removeReason = event.target.value;
1152 i.setState(i.state);
1155 handleModRemoveSubmit(i: PostListing) {
1156 event.preventDefault();
1157 let form: PostFormI = {
1158 name: i.props.post.name,
1159 community_id: i.props.post.community_id,
1160 edit_id: i.props.post.id,
1161 creator_id: i.props.post.creator_id,
1162 removed: !i.props.post.removed,
1163 reason: i.state.removeReason,
1164 nsfw: i.props.post.nsfw,
1167 WebSocketService.Instance.editPost(form);
1169 i.state.showRemoveDialog = false;
1170 i.setState(i.state);
1173 handleModLock(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 locked: !i.props.post.locked,
1183 WebSocketService.Instance.editPost(form);
1186 handleModSticky(i: PostListing) {
1187 let form: PostFormI = {
1188 name: i.props.post.name,
1189 community_id: i.props.post.community_id,
1190 edit_id: i.props.post.id,
1191 creator_id: i.props.post.creator_id,
1192 nsfw: i.props.post.nsfw,
1193 stickied: !i.props.post.stickied,
1196 WebSocketService.Instance.editPost(form);
1199 handleModBanFromCommunityShow(i: PostListing) {
1200 i.state.showBanDialog = true;
1201 i.state.banType = BanType.Community;
1202 i.setState(i.state);
1205 handleModBanShow(i: PostListing) {
1206 i.state.showBanDialog = true;
1207 i.state.banType = BanType.Site;
1208 i.setState(i.state);
1211 handleModBanReasonChange(i: PostListing, event: any) {
1212 i.state.banReason = event.target.value;
1213 i.setState(i.state);
1216 handleModBanExpiresChange(i: PostListing, event: any) {
1217 i.state.banExpires = event.target.value;
1218 i.setState(i.state);
1221 handleModBanFromCommunitySubmit(i: PostListing) {
1222 i.state.banType = BanType.Community;
1223 i.setState(i.state);
1224 i.handleModBanBothSubmit(i);
1227 handleModBanSubmit(i: PostListing) {
1228 i.state.banType = BanType.Site;
1229 i.setState(i.state);
1230 i.handleModBanBothSubmit(i);
1233 handleModBanBothSubmit(i: PostListing) {
1234 event.preventDefault();
1236 if (i.state.banType == BanType.Community) {
1237 let form: BanFromCommunityForm = {
1238 user_id: i.props.post.creator_id,
1239 community_id: i.props.post.community_id,
1240 ban: !i.props.post.banned_from_community,
1241 reason: i.state.banReason,
1242 expires: getUnixTime(i.state.banExpires),
1244 WebSocketService.Instance.banFromCommunity(form);
1246 let form: BanUserForm = {
1247 user_id: i.props.post.creator_id,
1248 ban: !i.props.post.banned,
1249 reason: i.state.banReason,
1250 expires: getUnixTime(i.state.banExpires),
1252 WebSocketService.Instance.banUser(form);
1255 i.state.showBanDialog = false;
1256 i.setState(i.state);
1259 handleAddModToCommunity(i: PostListing) {
1260 let form: AddModToCommunityForm = {
1261 user_id: i.props.post.creator_id,
1262 community_id: i.props.post.community_id,
1265 WebSocketService.Instance.addModToCommunity(form);
1266 i.setState(i.state);
1269 handleAddAdmin(i: PostListing) {
1270 let form: AddAdminForm = {
1271 user_id: i.props.post.creator_id,
1274 WebSocketService.Instance.addAdmin(form);
1275 i.setState(i.state);
1278 handleShowConfirmTransferCommunity(i: PostListing) {
1279 i.state.showConfirmTransferCommunity = true;
1280 i.setState(i.state);
1283 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1284 i.state.showConfirmTransferCommunity = false;
1285 i.setState(i.state);
1288 handleTransferCommunity(i: PostListing) {
1289 let form: TransferCommunityForm = {
1290 community_id: i.props.post.community_id,
1291 user_id: i.props.post.creator_id,
1293 WebSocketService.Instance.transferCommunity(form);
1294 i.state.showConfirmTransferCommunity = false;
1295 i.setState(i.state);
1298 handleShowConfirmTransferSite(i: PostListing) {
1299 i.state.showConfirmTransferSite = true;
1300 i.setState(i.state);
1303 handleCancelShowConfirmTransferSite(i: PostListing) {
1304 i.state.showConfirmTransferSite = false;
1305 i.setState(i.state);
1308 handleTransferSite(i: PostListing) {
1309 let form: TransferSiteForm = {
1310 user_id: i.props.post.creator_id,
1312 WebSocketService.Instance.transferSite(form);
1313 i.state.showConfirmTransferSite = false;
1314 i.setState(i.state);
1317 handleImageExpandClick(i: PostListing) {
1318 i.state.imageExpanded = !i.state.imageExpanded;
1319 i.setState(i.state);
1322 handleViewSource(i: PostListing) {
1323 i.state.viewSource = !i.state.viewSource;
1324 i.setState(i.state);
1327 handleShowAdvanced(i: PostListing) {
1328 i.state.showAdvanced = !i.state.showAdvanced;
1329 i.setState(i.state);
1333 get pointsTippy(): string {
1334 let points = i18n.t('number_of_points', {
1335 count: this.state.score,
1338 let upvotes = i18n.t('number_of_upvotes', {
1339 count: this.state.upvotes,
1342 let downvotes = i18n.t('number_of_downvotes', {
1343 count: this.state.downvotes,
1346 return `${points} • ${upvotes} • ${downvotes}`;