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';
38 import { i18n } from '../i18next';
39 import { CakeDay } from './cake-day';
41 interface PostListingState {
43 showRemoveDialog: boolean;
45 showBanDialog: boolean;
49 showConfirmTransferSite: boolean;
50 showConfirmTransferCommunity: boolean;
51 imageExpanded: boolean;
53 showAdvanced: boolean;
60 interface PostListingProps {
62 showCommunity?: boolean;
64 moderators?: Array<CommunityUser>;
65 admins?: Array<UserView>;
66 enableDownvotes: boolean;
70 export class PostListing extends Component<PostListingProps, PostListingState> {
71 private emptyState: PostListingState = {
73 showRemoveDialog: false,
78 banType: BanType.Community,
79 showConfirmTransferSite: false,
80 showConfirmTransferCommunity: false,
84 my_vote: this.props.post.my_vote,
85 score: this.props.post.score,
86 upvotes: this.props.post.upvotes,
87 downvotes: this.props.post.downvotes,
90 constructor(props: any, context: any) {
91 super(props, context);
93 this.state = this.emptyState;
94 this.handlePostLike = this.handlePostLike.bind(this);
95 this.handlePostDisLike = this.handlePostDisLike.bind(this);
96 this.handleEditPost = this.handleEditPost.bind(this);
97 this.handleEditCancel = this.handleEditCancel.bind(this);
100 componentWillReceiveProps(nextProps: PostListingProps) {
101 this.state.my_vote = nextProps.post.my_vote;
102 this.state.upvotes = nextProps.post.upvotes;
103 this.state.downvotes = nextProps.post.downvotes;
104 this.state.score = nextProps.post.score;
105 this.setState(this.state);
111 {!this.state.showEdit ? (
119 post={this.props.post}
120 onEdit={this.handleEditPost}
121 onCancel={this.handleEditCancel}
122 enableNsfw={this.props.enableNsfw}
123 enableDownvotes={this.props.enableDownvotes}
135 {this.props.post.url &&
136 this.props.showBody &&
137 this.props.post.embed_title && (
138 <IFramelyCard post={this.props.post} />
140 {this.props.showBody && this.props.post.body && (
142 {this.state.viewSource ? (
143 <pre>{this.props.post.body}</pre>
147 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
157 imgThumb(src: string) {
158 let post = this.props.post;
161 className={`img-fluid thumbnail rounded ${
162 (post.nsfw || post.community_nsfw) && 'img-blur'
169 getImage(thumbnail: boolean = false) {
170 let post = this.props.post;
171 if (isImage(post.url)) {
172 if (post.url.includes('pictrs')) {
173 return pictrsImage(post.url, thumbnail);
174 } else if (post.thumbnail_url) {
175 return pictrsImage(post.thumbnail_url, thumbnail);
179 } else if (post.thumbnail_url) {
180 return pictrsImage(post.thumbnail_url, thumbnail);
185 let post = this.props.post;
187 if (isImage(post.url)) {
190 class="text-body pointer"
191 data-tippy-content={i18n.t('expand_here')}
192 onClick={linkEvent(this, this.handleImageExpandClick)}
194 {this.imgThumb(this.getImage(true))}
195 <svg class="icon mini-overlay">
196 <use xlinkHref="#icon-image"></use>
200 } else if (post.thumbnail_url) {
203 className="text-body"
209 {this.imgThumb(this.getImage(true))}
210 <svg class="icon mini-overlay">
211 <use xlinkHref="#icon-external-link"></use>
215 } else if (post.url) {
216 if (isVideo(post.url)) {
218 <div class="embed-responsive embed-responsive-16by9">
224 class="embed-responsive-item"
226 <source src={post.url} type="video/mp4" />
233 className="text-body"
239 <svg class="icon thumbnail">
240 <use xlinkHref="#icon-external-link"></use>
248 className="text-body"
249 to={`/post/${post.id}`}
250 title={i18n.t('comments')}
252 <svg class="icon thumbnail">
253 <use xlinkHref="#icon-message-square"></use>
261 let post = this.props.post;
264 <div className={`vote-bar col-1 pr-0 small text-center`}>
266 className={`btn-animate btn btn-link p-0 ${
267 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
269 onClick={linkEvent(this, this.handlePostLike)}
270 data-tippy-content={i18n.t('upvote')}
272 <svg class="icon upvote">
273 <use xlinkHref="#icon-arrow-up1"></use>
277 class={`unselectable pointer font-weight-bold text-muted px-1`}
278 data-tippy-content={this.pointsTippy}
282 {this.props.enableDownvotes && (
284 className={`btn-animate btn btn-link p-0 ${
285 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
287 onClick={linkEvent(this, this.handlePostDisLike)}
288 data-tippy-content={i18n.t('downvote')}
290 <svg class="icon downvote">
291 <use xlinkHref="#icon-arrow-down1"></use>
296 {!this.state.imageExpanded && (
297 <div class="col-3 col-sm-2 pr-0 mt-1">
298 <div class="position-relative">{this.thumbnail()}</div>
302 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
305 <div className="col-12">
306 <div className="post-title">
307 <h5 className="mb-0 d-inline">
308 {this.props.showBody && post.url ? (
310 className="text-body"
320 className="text-body"
321 to={`/post/${post.id}`}
322 title={i18n.t('comments')}
328 {post.url && !(hostname(post.url) == window.location.hostname) && (
329 <small class="d-inline-block">
331 className="ml-2 text-muted font-italic"
338 <svg class="ml-1 icon icon-inline">
339 <use xlinkHref="#icon-external-link"></use>
344 {(isImage(post.url) || this.props.post.thumbnail_url) && (
346 {!this.state.imageExpanded ? (
348 class="text-monospace unselectable pointer ml-2 text-muted small"
349 data-tippy-content={i18n.t('expand_here')}
350 onClick={linkEvent(this, this.handleImageExpandClick)}
352 <svg class="icon icon-inline">
353 <use xlinkHref="#icon-plus-square"></use>
359 class="text-monospace unselectable pointer ml-2 text-muted small"
360 onClick={linkEvent(this, this.handleImageExpandClick)}
362 <svg class="icon icon-inline">
363 <use xlinkHref="#icon-minus-square"></use>
371 this.handleImageExpandClick
375 class="img-fluid img-expanded"
376 src={this.getImage()}
385 <small className="ml-2 text-muted font-italic">
391 className="unselectable pointer ml-2 text-muted font-italic"
392 data-tippy-content={i18n.t('deleted')}
394 <svg class={`icon icon-inline text-danger`}>
395 <use xlinkHref="#icon-trash"></use>
401 className="unselectable pointer ml-2 text-muted font-italic"
402 data-tippy-content={i18n.t('locked')}
404 <svg class={`icon icon-inline text-danger`}>
405 <use xlinkHref="#icon-lock"></use>
411 className="unselectable pointer ml-2 text-muted font-italic"
412 data-tippy-content={i18n.t('stickied')}
414 <svg class={`icon icon-inline text-success`}>
415 <use xlinkHref="#icon-pin"></use>
420 <small className="ml-2 text-muted font-italic">
428 <div className="details col-12">
429 <ul class="list-inline mb-0 text-muted small">
430 <li className="list-inline-item">
431 <span>{i18n.t('by')} </span>
434 name: post.creator_name,
435 avatar: post.creator_avatar,
437 local: post.creator_local,
438 actor_id: post.creator_actor_id,
442 {isCakeDay(post.creator_published) && (
443 <CakeDay creator_name={post.creator_name} is_post_creator />
447 <span className="mx-1 badge badge-light">
452 <span className="mx-1 badge badge-light">
456 {(post.banned_from_community || post.banned) && (
457 <span className="mx-1 badge badge-danger">
461 {this.props.showCommunity && (
463 <span> {i18n.t('to')} </span>
466 name: post.community_name,
467 id: post.community_id,
468 local: post.community_local,
469 actor_id: post.community_actor_id,
475 <li className="list-inline-item">•</li>
476 <li className="list-inline-item">
478 <MomentTime data={post} />
483 <li className="list-inline-item">•</li>
484 <li className="list-inline-item">
485 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
487 className="text-muted"
488 data-tippy-content={md.render(previewLines(post.body))}
489 data-tippy-allowHtml={true}
490 to={`/post/${post.id}`}
492 <svg class="mr-1 icon icon-inline">
493 <use xlinkHref="#icon-book-open"></use>
499 <li className="list-inline-item">•</li>
500 {this.state.upvotes !== this.state.score && (
503 class="unselectable pointer mr-2"
504 data-tippy-content={this.pointsTippy}
506 <li className="list-inline-item">
507 <span className="text-muted">
508 <svg class="small icon icon-inline mr-1">
509 <use xlinkHref="#icon-arrow-up"></use>
514 <li className="list-inline-item">
515 <span className="text-muted">
516 <svg class="small icon icon-inline mr-1">
517 <use xlinkHref="#icon-arrow-down"></use>
519 {this.state.downvotes}
523 <li className="list-inline-item">•</li>
526 <li className="list-inline-item">
528 className="text-muted"
529 title={i18n.t('number_of_comments', {
530 count: post.number_of_comments,
532 to={`/post/${post.id}`}
534 <svg class="mr-1 icon icon-inline">
535 <use xlinkHref="#icon-message-square"></use>
537 {post.number_of_comments}
541 {this.props.post.duplicates && (
542 <ul class="list-inline mb-1 small text-muted">
544 <li className="list-inline-item mr-2">
545 {i18n.t('cross_posted_to')}
547 {this.props.post.duplicates.map(post => (
548 <li className="list-inline-item mr-2">
549 <Link to={`/post/${post.id}`}>
550 {post.community_name}
557 <ul class="list-inline mb-1 text-muted font-weight-bold">
558 {UserService.Instance.user && (
560 {this.props.showBody && (
562 <li className="list-inline-item">
564 class="btn btn-sm btn-link btn-animate text-muted"
565 onClick={linkEvent(this, this.handleSavePostClick)}
567 post.saved ? i18n.t('unsave') : i18n.t('save')
571 class={`icon icon-inline ${
572 post.saved && 'text-warning'
575 <use xlinkHref="#icon-star"></use>
579 <li className="list-inline-item">
581 class="btn btn-sm btn-link btn-animate text-muted"
582 to={`/create_post${this.crossPostParams}`}
583 title={i18n.t('cross_post')}
585 <svg class="icon icon-inline">
586 <use xlinkHref="#icon-copy"></use>
592 {this.myPost && this.props.showBody && (
594 <li className="list-inline-item">
596 class="btn btn-sm btn-link btn-animate text-muted"
597 onClick={linkEvent(this, this.handleEditClick)}
598 data-tippy-content={i18n.t('edit')}
600 <svg class="icon icon-inline">
601 <use xlinkHref="#icon-edit"></use>
605 <li className="list-inline-item">
607 class="btn btn-sm btn-link btn-animate text-muted"
608 onClick={linkEvent(this, this.handleDeleteClick)}
616 class={`icon icon-inline ${
617 post.deleted && 'text-danger'
620 <use xlinkHref="#icon-trash"></use>
627 {!this.state.showAdvanced && this.props.showBody ? (
628 <li className="list-inline-item">
630 class="btn btn-sm btn-link btn-animate text-muted"
631 onClick={linkEvent(this, this.handleShowAdvanced)}
632 data-tippy-content={i18n.t('more')}
634 <svg class="icon icon-inline">
635 <use xlinkHref="#icon-more-vertical"></use>
641 {this.props.showBody && post.body && (
642 <li className="list-inline-item">
644 class="btn btn-sm btn-link btn-animate text-muted"
645 onClick={linkEvent(this, this.handleViewSource)}
646 data-tippy-content={i18n.t('view_source')}
649 class={`icon icon-inline ${
650 this.state.viewSource && 'text-success'
653 <use xlinkHref="#icon-file-text"></use>
658 {this.canModOnSelf && (
660 <li className="list-inline-item">
662 class="btn btn-sm btn-link btn-animate text-muted"
663 onClick={linkEvent(this, this.handleModLock)}
671 class={`icon icon-inline ${
672 post.locked && 'text-danger'
675 <use xlinkHref="#icon-lock"></use>
679 <li className="list-inline-item">
681 class="btn btn-sm btn-link btn-animate text-muted"
682 onClick={linkEvent(this, this.handleModSticky)}
690 class={`icon icon-inline ${
691 post.stickied && 'text-success'
694 <use xlinkHref="#icon-pin"></use>
700 {/* Mods can ban from community, and appoint as mods to community */}
701 {(this.canMod || this.canAdmin) && (
702 <li className="list-inline-item">
708 this.handleModRemoveShow
718 this.handleModRemoveSubmit
729 <li className="list-inline-item">
730 {!post.banned_from_community ? (
735 this.handleModBanFromCommunityShow
745 this.handleModBanFromCommunitySubmit
753 {!post.banned_from_community && (
754 <li className="list-inline-item">
759 this.handleAddModToCommunity
763 ? i18n.t('remove_as_mod')
764 : i18n.t('appoint_as_mod')}
770 {/* Community creators and admins can transfer community to another mod */}
771 {(this.amCommunityCreator || this.canAdmin) &&
773 <li className="list-inline-item">
774 {!this.state.showConfirmTransferCommunity ? (
779 this.handleShowConfirmTransferCommunity
782 {i18n.t('transfer_community')}
786 <span class="d-inline-block mr-1">
787 {i18n.t('are_you_sure')}
790 class="pointer d-inline-block mr-1"
793 this.handleTransferCommunity
799 class="pointer d-inline-block"
803 .handleCancelShowConfirmTransferCommunity
812 {/* Admins can ban from all, and appoint other admins */}
816 <li className="list-inline-item">
822 this.handleModBanShow
825 {i18n.t('ban_from_site')}
832 this.handleModBanSubmit
835 {i18n.t('unban_from_site')}
841 <li className="list-inline-item">
844 onClick={linkEvent(this, this.handleAddAdmin)}
847 ? i18n.t('remove_as_admin')
848 : i18n.t('appoint_as_admin')}
854 {/* Site Creator can transfer to another admin */}
855 {this.amSiteCreator && this.isAdmin && (
856 <li className="list-inline-item">
857 {!this.state.showConfirmTransferSite ? (
862 this.handleShowConfirmTransferSite
865 {i18n.t('transfer_site')}
869 <span class="d-inline-block mr-1">
870 {i18n.t('are_you_sure')}
873 class="pointer d-inline-block mr-1"
876 this.handleTransferSite
882 class="pointer d-inline-block"
885 this.handleCancelShowConfirmTransferSite
899 {this.state.showRemoveDialog && (
902 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
906 class="form-control mr-2"
907 placeholder={i18n.t('reason')}
908 value={this.state.removeReason}
909 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
911 <button type="submit" class="btn btn-secondary">
912 {i18n.t('remove_post')}
916 {this.state.showBanDialog && (
917 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
918 <div class="form-group row">
919 <label class="col-form-label" htmlFor="post-listing-reason">
924 id="post-listing-reason"
925 class="form-control mr-2"
926 placeholder={i18n.t('reason')}
927 value={this.state.banReason}
928 onInput={linkEvent(this, this.handleModBanReasonChange)}
931 {/* TODO hold off on expires until later */}
932 {/* <div class="form-group row"> */}
933 {/* <label class="col-form-label">Expires</label> */}
934 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
936 <div class="form-group row">
937 <button type="submit" class="btn btn-secondary">
938 {i18n.t('ban')} {post.creator_name}
950 private get myPost(): boolean {
952 UserService.Instance.user &&
953 this.props.post.creator_id == UserService.Instance.user.id
957 get isMod(): boolean {
959 this.props.moderators &&
961 this.props.moderators.map(m => m.user_id),
962 this.props.post.creator_id
967 get isAdmin(): boolean {
971 this.props.admins.map(a => a.id),
972 this.props.post.creator_id
977 get canMod(): boolean {
978 if (this.props.admins && this.props.moderators) {
979 let adminsThenMods = this.props.admins
981 .concat(this.props.moderators.map(m => m.user_id));
984 UserService.Instance.user,
986 this.props.post.creator_id
993 get canModOnSelf(): boolean {
994 if (this.props.admins && this.props.moderators) {
995 let adminsThenMods = this.props.admins
997 .concat(this.props.moderators.map(m => m.user_id));
1000 UserService.Instance.user,
1002 this.props.post.creator_id,
1010 get canAdmin(): boolean {
1012 this.props.admins &&
1014 UserService.Instance.user,
1015 this.props.admins.map(a => a.id),
1016 this.props.post.creator_id
1021 get amCommunityCreator(): boolean {
1023 this.props.moderators &&
1024 UserService.Instance.user &&
1025 this.props.post.creator_id != UserService.Instance.user.id &&
1026 UserService.Instance.user.id == this.props.moderators[0].user_id
1030 get amSiteCreator(): boolean {
1032 this.props.admins &&
1033 UserService.Instance.user &&
1034 this.props.post.creator_id != UserService.Instance.user.id &&
1035 UserService.Instance.user.id == this.props.admins[0].id
1039 handlePostLike(i: PostListing) {
1040 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1042 if (i.state.my_vote == 1) {
1045 } else if (i.state.my_vote == -1) {
1046 i.state.downvotes--;
1054 i.state.my_vote = new_vote;
1056 let form: CreatePostLikeForm = {
1057 post_id: i.props.post.id,
1058 score: i.state.my_vote,
1061 WebSocketService.Instance.likePost(form);
1062 i.setState(i.state);
1066 handlePostDisLike(i: PostListing) {
1067 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1069 if (i.state.my_vote == 1) {
1072 i.state.downvotes++;
1073 } else if (i.state.my_vote == -1) {
1074 i.state.downvotes--;
1077 i.state.downvotes++;
1081 i.state.my_vote = new_vote;
1083 let form: CreatePostLikeForm = {
1084 post_id: i.props.post.id,
1085 score: i.state.my_vote,
1088 WebSocketService.Instance.likePost(form);
1089 i.setState(i.state);
1093 handleEditClick(i: PostListing) {
1094 i.state.showEdit = true;
1095 i.setState(i.state);
1098 handleEditCancel() {
1099 this.state.showEdit = false;
1100 this.setState(this.state);
1103 // The actual editing is done in the recieve for post
1105 this.state.showEdit = false;
1106 this.setState(this.state);
1109 handleDeleteClick(i: PostListing) {
1110 let deleteForm: PostFormI = {
1111 body: i.props.post.body,
1112 community_id: i.props.post.community_id,
1113 name: i.props.post.name,
1114 url: i.props.post.url,
1115 edit_id: i.props.post.id,
1116 creator_id: i.props.post.creator_id,
1117 deleted: !i.props.post.deleted,
1118 nsfw: i.props.post.nsfw,
1121 WebSocketService.Instance.editPost(deleteForm);
1124 handleSavePostClick(i: PostListing) {
1125 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1126 let form: SavePostForm = {
1127 post_id: i.props.post.id,
1131 WebSocketService.Instance.savePost(form);
1134 get crossPostParams(): string {
1135 let params = `?title=${this.props.post.name}`;
1136 let post = this.props.post;
1139 params += `&url=${post.url}`;
1141 if (this.props.post.body) {
1142 params += `&body=${this.props.post.body}`;
1147 handleModRemoveShow(i: PostListing) {
1148 i.state.showRemoveDialog = true;
1149 i.setState(i.state);
1152 handleModRemoveReasonChange(i: PostListing, event: any) {
1153 i.state.removeReason = event.target.value;
1154 i.setState(i.state);
1157 handleModRemoveSubmit(i: PostListing) {
1158 event.preventDefault();
1159 let form: PostFormI = {
1160 name: i.props.post.name,
1161 community_id: i.props.post.community_id,
1162 edit_id: i.props.post.id,
1163 creator_id: i.props.post.creator_id,
1164 removed: !i.props.post.removed,
1165 reason: i.state.removeReason,
1166 nsfw: i.props.post.nsfw,
1169 WebSocketService.Instance.editPost(form);
1171 i.state.showRemoveDialog = false;
1172 i.setState(i.state);
1175 handleModLock(i: PostListing) {
1176 let form: PostFormI = {
1177 name: i.props.post.name,
1178 community_id: i.props.post.community_id,
1179 edit_id: i.props.post.id,
1180 creator_id: i.props.post.creator_id,
1181 nsfw: i.props.post.nsfw,
1182 locked: !i.props.post.locked,
1185 WebSocketService.Instance.editPost(form);
1188 handleModSticky(i: PostListing) {
1189 let form: PostFormI = {
1190 name: i.props.post.name,
1191 community_id: i.props.post.community_id,
1192 edit_id: i.props.post.id,
1193 creator_id: i.props.post.creator_id,
1194 nsfw: i.props.post.nsfw,
1195 stickied: !i.props.post.stickied,
1198 WebSocketService.Instance.editPost(form);
1201 handleModBanFromCommunityShow(i: PostListing) {
1202 i.state.showBanDialog = true;
1203 i.state.banType = BanType.Community;
1204 i.setState(i.state);
1207 handleModBanShow(i: PostListing) {
1208 i.state.showBanDialog = true;
1209 i.state.banType = BanType.Site;
1210 i.setState(i.state);
1213 handleModBanReasonChange(i: PostListing, event: any) {
1214 i.state.banReason = event.target.value;
1215 i.setState(i.state);
1218 handleModBanExpiresChange(i: PostListing, event: any) {
1219 i.state.banExpires = event.target.value;
1220 i.setState(i.state);
1223 handleModBanFromCommunitySubmit(i: PostListing) {
1224 i.state.banType = BanType.Community;
1225 i.setState(i.state);
1226 i.handleModBanBothSubmit(i);
1229 handleModBanSubmit(i: PostListing) {
1230 i.state.banType = BanType.Site;
1231 i.setState(i.state);
1232 i.handleModBanBothSubmit(i);
1235 handleModBanBothSubmit(i: PostListing) {
1236 event.preventDefault();
1238 if (i.state.banType == BanType.Community) {
1239 let form: BanFromCommunityForm = {
1240 user_id: i.props.post.creator_id,
1241 community_id: i.props.post.community_id,
1242 ban: !i.props.post.banned_from_community,
1243 reason: i.state.banReason,
1244 expires: getUnixTime(i.state.banExpires),
1246 WebSocketService.Instance.banFromCommunity(form);
1248 let form: BanUserForm = {
1249 user_id: i.props.post.creator_id,
1250 ban: !i.props.post.banned,
1251 reason: i.state.banReason,
1252 expires: getUnixTime(i.state.banExpires),
1254 WebSocketService.Instance.banUser(form);
1257 i.state.showBanDialog = false;
1258 i.setState(i.state);
1261 handleAddModToCommunity(i: PostListing) {
1262 let form: AddModToCommunityForm = {
1263 user_id: i.props.post.creator_id,
1264 community_id: i.props.post.community_id,
1267 WebSocketService.Instance.addModToCommunity(form);
1268 i.setState(i.state);
1271 handleAddAdmin(i: PostListing) {
1272 let form: AddAdminForm = {
1273 user_id: i.props.post.creator_id,
1276 WebSocketService.Instance.addAdmin(form);
1277 i.setState(i.state);
1280 handleShowConfirmTransferCommunity(i: PostListing) {
1281 i.state.showConfirmTransferCommunity = true;
1282 i.setState(i.state);
1285 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1286 i.state.showConfirmTransferCommunity = false;
1287 i.setState(i.state);
1290 handleTransferCommunity(i: PostListing) {
1291 let form: TransferCommunityForm = {
1292 community_id: i.props.post.community_id,
1293 user_id: i.props.post.creator_id,
1295 WebSocketService.Instance.transferCommunity(form);
1296 i.state.showConfirmTransferCommunity = false;
1297 i.setState(i.state);
1300 handleShowConfirmTransferSite(i: PostListing) {
1301 i.state.showConfirmTransferSite = true;
1302 i.setState(i.state);
1305 handleCancelShowConfirmTransferSite(i: PostListing) {
1306 i.state.showConfirmTransferSite = false;
1307 i.setState(i.state);
1310 handleTransferSite(i: PostListing) {
1311 let form: TransferSiteForm = {
1312 user_id: i.props.post.creator_id,
1314 WebSocketService.Instance.transferSite(form);
1315 i.state.showConfirmTransferSite = false;
1316 i.setState(i.state);
1319 handleImageExpandClick(i: PostListing) {
1320 i.state.imageExpanded = !i.state.imageExpanded;
1321 i.setState(i.state);
1324 handleViewSource(i: PostListing) {
1325 i.state.viewSource = !i.state.viewSource;
1326 i.setState(i.state);
1329 handleShowAdvanced(i: PostListing) {
1330 i.state.showAdvanced = !i.state.showAdvanced;
1331 i.setState(i.state);
1335 get pointsTippy(): string {
1336 let points = i18n.t('number_of_points', {
1337 count: this.state.score,
1340 let upvotes = i18n.t('number_of_upvotes', {
1341 count: this.state.upvotes,
1344 let downvotes = i18n.t('number_of_downvotes', {
1345 count: this.state.downvotes,
1348 return `${points} • ${upvotes} • ${downvotes}`;