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>;
65 enableDownvotes: boolean;
69 export class PostListing extends Component<PostListingProps, PostListingState> {
70 private emptyState: PostListingState = {
72 showRemoveDialog: false,
77 banType: BanType.Community,
78 showConfirmTransferSite: false,
79 showConfirmTransferCommunity: false,
83 my_vote: this.props.post.my_vote,
84 score: this.props.post.score,
85 upvotes: this.props.post.upvotes,
86 downvotes: this.props.post.downvotes,
89 constructor(props: any, context: any) {
90 super(props, context);
92 this.state = this.emptyState;
93 this.handlePostLike = this.handlePostLike.bind(this);
94 this.handlePostDisLike = this.handlePostDisLike.bind(this);
95 this.handleEditPost = this.handleEditPost.bind(this);
96 this.handleEditCancel = this.handleEditCancel.bind(this);
99 componentWillReceiveProps(nextProps: PostListingProps) {
100 this.state.my_vote = nextProps.post.my_vote;
101 this.state.upvotes = nextProps.post.upvotes;
102 this.state.downvotes = nextProps.post.downvotes;
103 this.state.score = nextProps.post.score;
104 this.setState(this.state);
110 {!this.state.showEdit ? (
118 post={this.props.post}
119 onEdit={this.handleEditPost}
120 onCancel={this.handleEditCancel}
121 enableNsfw={this.props.enableNsfw}
122 enableDownvotes={this.props.enableDownvotes}
134 {this.props.post.url &&
135 this.props.showBody &&
136 this.props.post.embed_title && (
137 <IFramelyCard post={this.props.post} />
139 {this.props.showBody && this.props.post.body && (
141 {this.state.viewSource ? (
142 <pre>{this.props.post.body}</pre>
146 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
156 imgThumb(src: string) {
157 let post = this.props.post;
160 className={`img-fluid thumbnail rounded ${
161 (post.nsfw || post.community_nsfw) && 'img-blur'
168 getImage(thumbnail: boolean = false) {
169 let post = this.props.post;
170 if (isImage(post.url)) {
171 if (post.url.includes('pictrs')) {
172 return pictrsImage(post.url, thumbnail);
173 } else if (post.thumbnail_url) {
174 return pictrsImage(post.thumbnail_url, thumbnail);
178 } else if (post.thumbnail_url) {
179 return pictrsImage(post.thumbnail_url, thumbnail);
184 let post = this.props.post;
186 if (isImage(post.url)) {
189 class="text-body pointer"
190 data-tippy-content={i18n.t('expand_here')}
191 onClick={linkEvent(this, this.handleImageExpandClick)}
193 {this.imgThumb(this.getImage(true))}
194 <svg class="icon mini-overlay">
195 <use xlinkHref="#icon-image"></use>
199 } else if (post.thumbnail_url) {
202 className="text-body"
208 {this.imgThumb(this.getImage(true))}
209 <svg class="icon mini-overlay">
210 <use xlinkHref="#icon-external-link"></use>
214 } else if (post.url) {
215 if (isVideo(post.url)) {
217 <div class="embed-responsive embed-responsive-16by9">
223 class="embed-responsive-item"
225 <source src={post.url} type="video/mp4" />
232 className="text-body"
238 <svg class="icon thumbnail">
239 <use xlinkHref="#icon-external-link"></use>
247 className="text-body"
248 to={`/post/${post.id}`}
249 title={i18n.t('comments')}
251 <svg class="icon thumbnail">
252 <use xlinkHref="#icon-message-square"></use>
260 let post = this.props.post;
261 const { creator_name, creator_published } = post;
265 <div className={`vote-bar col-1 pr-0 small text-center`}>
267 className={`btn-animate btn btn-link p-0 ${
268 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
270 onClick={linkEvent(this, this.handlePostLike)}
271 data-tippy-content={i18n.t('upvote')}
273 <svg class="icon upvote">
274 <use xlinkHref="#icon-arrow-up1"></use>
278 class={`unselectable pointer font-weight-bold text-muted px-1`}
279 data-tippy-content={this.pointsTippy}
283 {this.props.enableDownvotes && (
285 className={`btn-animate btn btn-link p-0 ${
286 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
288 onClick={linkEvent(this, this.handlePostDisLike)}
289 data-tippy-content={i18n.t('downvote')}
291 <svg class="icon downvote">
292 <use xlinkHref="#icon-arrow-down1"></use>
297 {!this.state.imageExpanded && (
298 <div class="col-3 col-sm-2 pr-0 mt-1">
299 <div class="position-relative">{this.thumbnail()}</div>
303 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
306 <div className="col-12">
307 <div className="post-title">
308 <h5 className="mb-0 d-inline">
309 {this.props.showBody && post.url ? (
311 className="text-body"
321 className="text-body"
322 to={`/post/${post.id}`}
323 title={i18n.t('comments')}
329 {post.url && !(hostname(post.url) == window.location.hostname) && (
330 <small class="d-inline-block">
332 className="ml-2 text-muted font-italic"
339 <svg class="ml-1 icon icon-inline">
340 <use xlinkHref="#icon-external-link"></use>
345 {(isImage(post.url) || this.props.post.thumbnail_url) && (
347 {!this.state.imageExpanded ? (
349 class="text-monospace unselectable pointer ml-2 text-muted small"
350 data-tippy-content={i18n.t('expand_here')}
351 onClick={linkEvent(this, this.handleImageExpandClick)}
353 <svg class="icon icon-inline">
354 <use xlinkHref="#icon-plus-square"></use>
360 class="text-monospace unselectable pointer ml-2 text-muted small"
361 onClick={linkEvent(this, this.handleImageExpandClick)}
363 <svg class="icon icon-inline">
364 <use xlinkHref="#icon-minus-square"></use>
372 this.handleImageExpandClick
376 class="img-fluid img-expanded"
377 src={this.getImage()}
386 <small className="ml-2 text-muted font-italic">
392 className="unselectable pointer ml-2 text-muted font-italic"
393 data-tippy-content={i18n.t('deleted')}
395 <svg class={`icon icon-inline text-danger`}>
396 <use xlinkHref="#icon-trash"></use>
402 className="unselectable pointer ml-2 text-muted font-italic"
403 data-tippy-content={i18n.t('locked')}
405 <svg class={`icon icon-inline text-danger`}>
406 <use xlinkHref="#icon-lock"></use>
412 className="unselectable pointer ml-2 text-muted font-italic"
413 data-tippy-content={i18n.t('stickied')}
415 <svg class={`icon icon-inline text-success`}>
416 <use xlinkHref="#icon-pin"></use>
421 <small className="ml-2 text-muted font-italic">
429 <div className="details col-12">
430 <ul class="list-inline mb-0 text-muted small">
431 <li className="list-inline-item">
432 <span>{i18n.t('by')} </span>
435 name: post.creator_name,
436 avatar: post.creator_avatar,
438 local: post.creator_local,
439 actor_id: post.creator_actor_id,
444 creator_name={creator_name}
445 creator_published={creator_published}
449 <span className="mx-1 badge badge-light">
454 <span className="mx-1 badge badge-light">
458 {(post.banned_from_community || post.banned) && (
459 <span className="mx-1 badge badge-danger">
463 {this.props.showCommunity && (
465 <span> {i18n.t('to')} </span>
468 name: post.community_name,
469 id: post.community_id,
470 local: post.community_local,
471 actor_id: post.community_actor_id,
477 <li className="list-inline-item">•</li>
478 <li className="list-inline-item">
480 <MomentTime data={post} />
485 <li className="list-inline-item">•</li>
486 <li className="list-inline-item">
487 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
489 className="text-muted"
490 data-tippy-content={md.render(previewLines(post.body))}
491 data-tippy-allowHtml={true}
492 to={`/post/${post.id}`}
494 <svg class="mr-1 icon icon-inline">
495 <use xlinkHref="#icon-book-open"></use>
501 <li className="list-inline-item">•</li>
502 {this.state.upvotes !== this.state.score && (
505 class="unselectable pointer mr-2"
506 data-tippy-content={this.pointsTippy}
508 <li className="list-inline-item">
509 <span className="text-muted">
510 <svg class="small icon icon-inline mr-1">
511 <use xlinkHref="#icon-arrow-up"></use>
516 <li className="list-inline-item">
517 <span className="text-muted">
518 <svg class="small icon icon-inline mr-1">
519 <use xlinkHref="#icon-arrow-down"></use>
521 {this.state.downvotes}
525 <li className="list-inline-item">•</li>
528 <li className="list-inline-item">
530 className="text-muted"
531 title={i18n.t('number_of_comments', {
532 count: post.number_of_comments,
534 to={`/post/${post.id}`}
536 <svg class="mr-1 icon icon-inline">
537 <use xlinkHref="#icon-message-square"></use>
539 {post.number_of_comments}
543 {this.props.post.duplicates && (
544 <ul class="list-inline mb-1 small text-muted">
546 <li className="list-inline-item mr-2">
547 {i18n.t('cross_posted_to')}
549 {this.props.post.duplicates.map(post => (
550 <li className="list-inline-item mr-2">
551 <Link to={`/post/${post.id}`}>
552 {post.community_name}
559 <ul class="list-inline mb-1 text-muted font-weight-bold">
560 {UserService.Instance.user && (
562 {this.props.showBody && (
564 <li className="list-inline-item">
566 class="btn btn-sm btn-link btn-animate text-muted"
567 onClick={linkEvent(this, this.handleSavePostClick)}
569 post.saved ? i18n.t('unsave') : i18n.t('save')
573 class={`icon icon-inline ${
574 post.saved && 'text-warning'
577 <use xlinkHref="#icon-star"></use>
581 <li className="list-inline-item">
583 class="btn btn-sm btn-link btn-animate text-muted"
584 to={`/create_post${this.crossPostParams}`}
585 title={i18n.t('cross_post')}
587 <svg class="icon icon-inline">
588 <use xlinkHref="#icon-copy"></use>
594 {this.myPost && this.props.showBody && (
596 <li className="list-inline-item">
598 class="btn btn-sm btn-link btn-animate text-muted"
599 onClick={linkEvent(this, this.handleEditClick)}
600 data-tippy-content={i18n.t('edit')}
602 <svg class="icon icon-inline">
603 <use xlinkHref="#icon-edit"></use>
607 <li className="list-inline-item">
609 class="btn btn-sm btn-link btn-animate text-muted"
610 onClick={linkEvent(this, this.handleDeleteClick)}
618 class={`icon icon-inline ${
619 post.deleted && 'text-danger'
622 <use xlinkHref="#icon-trash"></use>
629 {!this.state.showAdvanced && this.props.showBody ? (
630 <li className="list-inline-item">
632 class="btn btn-sm btn-link btn-animate text-muted"
633 onClick={linkEvent(this, this.handleShowAdvanced)}
634 data-tippy-content={i18n.t('more')}
636 <svg class="icon icon-inline">
637 <use xlinkHref="#icon-more-vertical"></use>
643 {this.props.showBody && post.body && (
644 <li className="list-inline-item">
646 class="btn btn-sm btn-link btn-animate text-muted"
647 onClick={linkEvent(this, this.handleViewSource)}
648 data-tippy-content={i18n.t('view_source')}
651 class={`icon icon-inline ${
652 this.state.viewSource && 'text-success'
655 <use xlinkHref="#icon-file-text"></use>
660 {this.canModOnSelf && (
662 <li className="list-inline-item">
664 class="btn btn-sm btn-link btn-animate text-muted"
665 onClick={linkEvent(this, this.handleModLock)}
673 class={`icon icon-inline ${
674 post.locked && 'text-danger'
677 <use xlinkHref="#icon-lock"></use>
681 <li className="list-inline-item">
683 class="btn btn-sm btn-link btn-animate text-muted"
684 onClick={linkEvent(this, this.handleModSticky)}
692 class={`icon icon-inline ${
693 post.stickied && 'text-success'
696 <use xlinkHref="#icon-pin"></use>
702 {/* Mods can ban from community, and appoint as mods to community */}
703 {(this.canMod || this.canAdmin) && (
704 <li className="list-inline-item">
710 this.handleModRemoveShow
720 this.handleModRemoveSubmit
731 <li className="list-inline-item">
732 {!post.banned_from_community ? (
737 this.handleModBanFromCommunityShow
747 this.handleModBanFromCommunitySubmit
755 {!post.banned_from_community && (
756 <li className="list-inline-item">
761 this.handleAddModToCommunity
765 ? i18n.t('remove_as_mod')
766 : i18n.t('appoint_as_mod')}
772 {/* Community creators and admins can transfer community to another mod */}
773 {(this.amCommunityCreator || this.canAdmin) &&
775 <li className="list-inline-item">
776 {!this.state.showConfirmTransferCommunity ? (
781 this.handleShowConfirmTransferCommunity
784 {i18n.t('transfer_community')}
788 <span class="d-inline-block mr-1">
789 {i18n.t('are_you_sure')}
792 class="pointer d-inline-block mr-1"
795 this.handleTransferCommunity
801 class="pointer d-inline-block"
805 .handleCancelShowConfirmTransferCommunity
814 {/* Admins can ban from all, and appoint other admins */}
818 <li className="list-inline-item">
824 this.handleModBanShow
827 {i18n.t('ban_from_site')}
834 this.handleModBanSubmit
837 {i18n.t('unban_from_site')}
843 <li className="list-inline-item">
846 onClick={linkEvent(this, this.handleAddAdmin)}
849 ? i18n.t('remove_as_admin')
850 : i18n.t('appoint_as_admin')}
856 {/* Site Creator can transfer to another admin */}
857 {this.amSiteCreator && this.isAdmin && (
858 <li className="list-inline-item">
859 {!this.state.showConfirmTransferSite ? (
864 this.handleShowConfirmTransferSite
867 {i18n.t('transfer_site')}
871 <span class="d-inline-block mr-1">
872 {i18n.t('are_you_sure')}
875 class="pointer d-inline-block mr-1"
878 this.handleTransferSite
884 class="pointer d-inline-block"
887 this.handleCancelShowConfirmTransferSite
901 {this.state.showRemoveDialog && (
904 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
908 class="form-control mr-2"
909 placeholder={i18n.t('reason')}
910 value={this.state.removeReason}
911 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
913 <button type="submit" class="btn btn-secondary">
914 {i18n.t('remove_post')}
918 {this.state.showBanDialog && (
919 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
920 <div class="form-group row">
921 <label class="col-form-label" htmlFor="post-listing-reason">
926 id="post-listing-reason"
927 class="form-control mr-2"
928 placeholder={i18n.t('reason')}
929 value={this.state.banReason}
930 onInput={linkEvent(this, this.handleModBanReasonChange)}
933 {/* TODO hold off on expires until later */}
934 {/* <div class="form-group row"> */}
935 {/* <label class="col-form-label">Expires</label> */}
936 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
938 <div class="form-group row">
939 <button type="submit" class="btn btn-secondary">
940 {i18n.t('ban')} {post.creator_name}
952 private get myPost(): boolean {
954 UserService.Instance.user &&
955 this.props.post.creator_id == UserService.Instance.user.id
959 get isMod(): boolean {
961 this.props.moderators &&
963 this.props.moderators.map(m => m.user_id),
964 this.props.post.creator_id
969 get isAdmin(): boolean {
973 this.props.admins.map(a => a.id),
974 this.props.post.creator_id
979 get canMod(): boolean {
980 if (this.props.admins && this.props.moderators) {
981 let adminsThenMods = this.props.admins
983 .concat(this.props.moderators.map(m => m.user_id));
986 UserService.Instance.user,
988 this.props.post.creator_id
995 get canModOnSelf(): boolean {
996 if (this.props.admins && this.props.moderators) {
997 let adminsThenMods = this.props.admins
999 .concat(this.props.moderators.map(m => m.user_id));
1002 UserService.Instance.user,
1004 this.props.post.creator_id,
1012 get canAdmin(): boolean {
1014 this.props.admins &&
1016 UserService.Instance.user,
1017 this.props.admins.map(a => a.id),
1018 this.props.post.creator_id
1023 get amCommunityCreator(): boolean {
1025 this.props.moderators &&
1026 UserService.Instance.user &&
1027 this.props.post.creator_id != UserService.Instance.user.id &&
1028 UserService.Instance.user.id == this.props.moderators[0].user_id
1032 get amSiteCreator(): boolean {
1034 this.props.admins &&
1035 UserService.Instance.user &&
1036 this.props.post.creator_id != UserService.Instance.user.id &&
1037 UserService.Instance.user.id == this.props.admins[0].id
1041 handlePostLike(i: PostListing) {
1042 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1044 if (i.state.my_vote == 1) {
1047 } else if (i.state.my_vote == -1) {
1048 i.state.downvotes--;
1056 i.state.my_vote = new_vote;
1058 let form: CreatePostLikeForm = {
1059 post_id: i.props.post.id,
1060 score: i.state.my_vote,
1063 WebSocketService.Instance.likePost(form);
1064 i.setState(i.state);
1068 handlePostDisLike(i: PostListing) {
1069 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1071 if (i.state.my_vote == 1) {
1074 i.state.downvotes++;
1075 } else if (i.state.my_vote == -1) {
1076 i.state.downvotes--;
1079 i.state.downvotes++;
1083 i.state.my_vote = new_vote;
1085 let form: CreatePostLikeForm = {
1086 post_id: i.props.post.id,
1087 score: i.state.my_vote,
1090 WebSocketService.Instance.likePost(form);
1091 i.setState(i.state);
1095 handleEditClick(i: PostListing) {
1096 i.state.showEdit = true;
1097 i.setState(i.state);
1100 handleEditCancel() {
1101 this.state.showEdit = false;
1102 this.setState(this.state);
1105 // The actual editing is done in the recieve for post
1107 this.state.showEdit = false;
1108 this.setState(this.state);
1111 handleDeleteClick(i: PostListing) {
1112 let deleteForm: PostFormI = {
1113 body: i.props.post.body,
1114 community_id: i.props.post.community_id,
1115 name: i.props.post.name,
1116 url: i.props.post.url,
1117 edit_id: i.props.post.id,
1118 creator_id: i.props.post.creator_id,
1119 deleted: !i.props.post.deleted,
1120 nsfw: i.props.post.nsfw,
1123 WebSocketService.Instance.editPost(deleteForm);
1126 handleSavePostClick(i: PostListing) {
1127 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1128 let form: SavePostForm = {
1129 post_id: i.props.post.id,
1133 WebSocketService.Instance.savePost(form);
1136 get crossPostParams(): string {
1137 let params = `?title=${this.props.post.name}`;
1138 let post = this.props.post;
1141 params += `&url=${post.url}`;
1143 if (this.props.post.body) {
1144 params += `&body=${this.props.post.body}`;
1149 handleModRemoveShow(i: PostListing) {
1150 i.state.showRemoveDialog = true;
1151 i.setState(i.state);
1154 handleModRemoveReasonChange(i: PostListing, event: any) {
1155 i.state.removeReason = event.target.value;
1156 i.setState(i.state);
1159 handleModRemoveSubmit(i: PostListing) {
1160 event.preventDefault();
1161 let form: PostFormI = {
1162 name: i.props.post.name,
1163 community_id: i.props.post.community_id,
1164 edit_id: i.props.post.id,
1165 creator_id: i.props.post.creator_id,
1166 removed: !i.props.post.removed,
1167 reason: i.state.removeReason,
1168 nsfw: i.props.post.nsfw,
1171 WebSocketService.Instance.editPost(form);
1173 i.state.showRemoveDialog = false;
1174 i.setState(i.state);
1177 handleModLock(i: PostListing) {
1178 let form: PostFormI = {
1179 name: i.props.post.name,
1180 community_id: i.props.post.community_id,
1181 edit_id: i.props.post.id,
1182 creator_id: i.props.post.creator_id,
1183 nsfw: i.props.post.nsfw,
1184 locked: !i.props.post.locked,
1187 WebSocketService.Instance.editPost(form);
1190 handleModSticky(i: PostListing) {
1191 let form: PostFormI = {
1192 name: i.props.post.name,
1193 community_id: i.props.post.community_id,
1194 edit_id: i.props.post.id,
1195 creator_id: i.props.post.creator_id,
1196 nsfw: i.props.post.nsfw,
1197 stickied: !i.props.post.stickied,
1200 WebSocketService.Instance.editPost(form);
1203 handleModBanFromCommunityShow(i: PostListing) {
1204 i.state.showBanDialog = true;
1205 i.state.banType = BanType.Community;
1206 i.setState(i.state);
1209 handleModBanShow(i: PostListing) {
1210 i.state.showBanDialog = true;
1211 i.state.banType = BanType.Site;
1212 i.setState(i.state);
1215 handleModBanReasonChange(i: PostListing, event: any) {
1216 i.state.banReason = event.target.value;
1217 i.setState(i.state);
1220 handleModBanExpiresChange(i: PostListing, event: any) {
1221 i.state.banExpires = event.target.value;
1222 i.setState(i.state);
1225 handleModBanFromCommunitySubmit(i: PostListing) {
1226 i.state.banType = BanType.Community;
1227 i.setState(i.state);
1228 i.handleModBanBothSubmit(i);
1231 handleModBanSubmit(i: PostListing) {
1232 i.state.banType = BanType.Site;
1233 i.setState(i.state);
1234 i.handleModBanBothSubmit(i);
1237 handleModBanBothSubmit(i: PostListing) {
1238 event.preventDefault();
1240 if (i.state.banType == BanType.Community) {
1241 let form: BanFromCommunityForm = {
1242 user_id: i.props.post.creator_id,
1243 community_id: i.props.post.community_id,
1244 ban: !i.props.post.banned_from_community,
1245 reason: i.state.banReason,
1246 expires: getUnixTime(i.state.banExpires),
1248 WebSocketService.Instance.banFromCommunity(form);
1250 let form: BanUserForm = {
1251 user_id: i.props.post.creator_id,
1252 ban: !i.props.post.banned,
1253 reason: i.state.banReason,
1254 expires: getUnixTime(i.state.banExpires),
1256 WebSocketService.Instance.banUser(form);
1259 i.state.showBanDialog = false;
1260 i.setState(i.state);
1263 handleAddModToCommunity(i: PostListing) {
1264 let form: AddModToCommunityForm = {
1265 user_id: i.props.post.creator_id,
1266 community_id: i.props.post.community_id,
1269 WebSocketService.Instance.addModToCommunity(form);
1270 i.setState(i.state);
1273 handleAddAdmin(i: PostListing) {
1274 let form: AddAdminForm = {
1275 user_id: i.props.post.creator_id,
1278 WebSocketService.Instance.addAdmin(form);
1279 i.setState(i.state);
1282 handleShowConfirmTransferCommunity(i: PostListing) {
1283 i.state.showConfirmTransferCommunity = true;
1284 i.setState(i.state);
1287 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1288 i.state.showConfirmTransferCommunity = false;
1289 i.setState(i.state);
1292 handleTransferCommunity(i: PostListing) {
1293 let form: TransferCommunityForm = {
1294 community_id: i.props.post.community_id,
1295 user_id: i.props.post.creator_id,
1297 WebSocketService.Instance.transferCommunity(form);
1298 i.state.showConfirmTransferCommunity = false;
1299 i.setState(i.state);
1302 handleShowConfirmTransferSite(i: PostListing) {
1303 i.state.showConfirmTransferSite = true;
1304 i.setState(i.state);
1307 handleCancelShowConfirmTransferSite(i: PostListing) {
1308 i.state.showConfirmTransferSite = false;
1309 i.setState(i.state);
1312 handleTransferSite(i: PostListing) {
1313 let form: TransferSiteForm = {
1314 user_id: i.props.post.creator_id,
1316 WebSocketService.Instance.transferSite(form);
1317 i.state.showConfirmTransferSite = false;
1318 i.setState(i.state);
1321 handleImageExpandClick(i: PostListing) {
1322 i.state.imageExpanded = !i.state.imageExpanded;
1323 i.setState(i.state);
1326 handleViewSource(i: PostListing) {
1327 i.state.viewSource = !i.state.viewSource;
1328 i.setState(i.state);
1331 handleShowAdvanced(i: PostListing) {
1332 i.state.showAdvanced = !i.state.showAdvanced;
1333 i.setState(i.state);
1337 get pointsTippy(): string {
1338 let points = i18n.t('number_of_points', {
1339 count: this.state.score,
1342 let upvotes = i18n.t('number_of_upvotes', {
1343 count: this.state.upvotes,
1346 let downvotes = i18n.t('number_of_downvotes', {
1347 count: this.state.downvotes,
1350 return `${points} • ${upvotes} • ${downvotes}`;