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';
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;
263 <div className={`vote-bar col-1 pr-0 small text-center`}>
265 className={`btn-animate btn btn-link p-0 ${
266 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
268 onClick={linkEvent(this, this.handlePostLike)}
269 data-tippy-content={i18n.t('upvote')}
271 <svg class="icon upvote">
272 <use xlinkHref="#icon-arrow-up1"></use>
276 class={`unselectable pointer font-weight-bold text-muted px-1`}
277 data-tippy-content={this.pointsTippy}
281 {this.props.enableDownvotes && (
283 className={`btn-animate btn btn-link p-0 ${
284 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
286 onClick={linkEvent(this, this.handlePostDisLike)}
287 data-tippy-content={i18n.t('downvote')}
289 <svg class="icon downvote">
290 <use xlinkHref="#icon-arrow-down1"></use>
295 {!this.state.imageExpanded && (
296 <div class="col-3 col-sm-2 pr-0 mt-1">
297 <div class="position-relative">{this.thumbnail()}</div>
301 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
304 <div className="col-12">
305 <div className="post-title">
306 <h5 className="mb-0 d-inline">
307 {this.props.showBody && post.url ? (
309 className="text-body"
319 className="text-body"
320 to={`/post/${post.id}`}
321 title={i18n.t('comments')}
327 {post.url && !(hostname(post.url) == window.location.hostname) && (
328 <small class="d-inline-block">
330 className="ml-2 text-muted font-italic"
337 <svg class="ml-1 icon icon-inline">
338 <use xlinkHref="#icon-external-link"></use>
343 {(isImage(post.url) || this.props.post.thumbnail_url) && (
345 {!this.state.imageExpanded ? (
347 class="text-monospace unselectable pointer ml-2 text-muted small"
348 data-tippy-content={i18n.t('expand_here')}
349 onClick={linkEvent(this, this.handleImageExpandClick)}
351 <svg class="icon icon-inline">
352 <use xlinkHref="#icon-plus-square"></use>
358 class="text-monospace unselectable pointer ml-2 text-muted small"
359 onClick={linkEvent(this, this.handleImageExpandClick)}
361 <svg class="icon icon-inline">
362 <use xlinkHref="#icon-minus-square"></use>
370 this.handleImageExpandClick
374 class="img-fluid img-expanded"
375 src={this.getImage()}
384 <small className="ml-2 text-muted font-italic">
390 className="unselectable pointer ml-2 text-muted font-italic"
391 data-tippy-content={i18n.t('deleted')}
393 <svg class={`icon icon-inline text-danger`}>
394 <use xlinkHref="#icon-trash"></use>
400 className="unselectable pointer ml-2 text-muted font-italic"
401 data-tippy-content={i18n.t('locked')}
403 <svg class={`icon icon-inline text-danger`}>
404 <use xlinkHref="#icon-lock"></use>
410 className="unselectable pointer ml-2 text-muted font-italic"
411 data-tippy-content={i18n.t('stickied')}
413 <svg class={`icon icon-inline text-success`}>
414 <use xlinkHref="#icon-pin"></use>
419 <small className="ml-2 text-muted font-italic">
427 <div className="details col-12">
428 <ul class="list-inline mb-0 text-muted small">
429 <li className="list-inline-item">
430 <span>{i18n.t('by')} </span>
433 name: post.creator_name,
434 avatar: post.creator_avatar,
436 local: post.creator_local,
437 actor_id: post.creator_actor_id,
438 published: post.creator_published,
443 <span className="mx-1 badge badge-light">
448 <span className="mx-1 badge badge-light">
452 {(post.banned_from_community || post.banned) && (
453 <span className="mx-1 badge badge-danger">
457 {this.props.showCommunity && (
459 <span> {i18n.t('to')} </span>
462 name: post.community_name,
463 id: post.community_id,
464 local: post.community_local,
465 actor_id: post.community_actor_id,
471 <li className="list-inline-item">•</li>
472 <li className="list-inline-item">
474 <MomentTime data={post} />
479 <li className="list-inline-item">•</li>
480 <li className="list-inline-item">
481 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
483 className="text-muted"
484 data-tippy-content={md.render(previewLines(post.body))}
485 data-tippy-allowHtml={true}
486 to={`/post/${post.id}`}
488 <svg class="mr-1 icon icon-inline">
489 <use xlinkHref="#icon-book-open"></use>
495 <li className="list-inline-item">•</li>
496 {this.state.upvotes !== this.state.score && (
499 class="unselectable pointer mr-2"
500 data-tippy-content={this.pointsTippy}
502 <li className="list-inline-item">
503 <span className="text-muted">
504 <svg class="small icon icon-inline mr-1">
505 <use xlinkHref="#icon-arrow-up"></use>
510 <li className="list-inline-item">
511 <span className="text-muted">
512 <svg class="small icon icon-inline mr-1">
513 <use xlinkHref="#icon-arrow-down"></use>
515 {this.state.downvotes}
519 <li className="list-inline-item">•</li>
522 <li className="list-inline-item">
524 className="text-muted"
525 title={i18n.t('number_of_comments', {
526 count: post.number_of_comments,
528 to={`/post/${post.id}`}
530 <svg class="mr-1 icon icon-inline">
531 <use xlinkHref="#icon-message-square"></use>
533 {post.number_of_comments}
537 {this.props.post.duplicates && (
538 <ul class="list-inline mb-1 small text-muted">
540 <li className="list-inline-item mr-2">
541 {i18n.t('cross_posted_to')}
543 {this.props.post.duplicates.map(post => (
544 <li className="list-inline-item mr-2">
545 <Link to={`/post/${post.id}`}>
546 {post.community_name}
553 <ul class="list-inline mb-1 text-muted font-weight-bold">
554 {UserService.Instance.user && (
556 {this.props.showBody && (
558 <li className="list-inline-item">
560 class="btn btn-sm btn-link btn-animate text-muted"
561 onClick={linkEvent(this, this.handleSavePostClick)}
563 post.saved ? i18n.t('unsave') : i18n.t('save')
567 class={`icon icon-inline ${
568 post.saved && 'text-warning'
571 <use xlinkHref="#icon-star"></use>
575 <li className="list-inline-item">
577 class="btn btn-sm btn-link btn-animate text-muted"
578 to={`/create_post${this.crossPostParams}`}
579 title={i18n.t('cross_post')}
581 <svg class="icon icon-inline">
582 <use xlinkHref="#icon-copy"></use>
588 {this.myPost && this.props.showBody && (
590 <li className="list-inline-item">
592 class="btn btn-sm btn-link btn-animate text-muted"
593 onClick={linkEvent(this, this.handleEditClick)}
594 data-tippy-content={i18n.t('edit')}
596 <svg class="icon icon-inline">
597 <use xlinkHref="#icon-edit"></use>
601 <li className="list-inline-item">
603 class="btn btn-sm btn-link btn-animate text-muted"
604 onClick={linkEvent(this, this.handleDeleteClick)}
612 class={`icon icon-inline ${
613 post.deleted && 'text-danger'
616 <use xlinkHref="#icon-trash"></use>
623 {!this.state.showAdvanced && this.props.showBody ? (
624 <li className="list-inline-item">
626 class="btn btn-sm btn-link btn-animate text-muted"
627 onClick={linkEvent(this, this.handleShowAdvanced)}
628 data-tippy-content={i18n.t('more')}
630 <svg class="icon icon-inline">
631 <use xlinkHref="#icon-more-vertical"></use>
637 {this.props.showBody && post.body && (
638 <li className="list-inline-item">
640 class="btn btn-sm btn-link btn-animate text-muted"
641 onClick={linkEvent(this, this.handleViewSource)}
642 data-tippy-content={i18n.t('view_source')}
645 class={`icon icon-inline ${
646 this.state.viewSource && 'text-success'
649 <use xlinkHref="#icon-file-text"></use>
654 {this.canModOnSelf && (
656 <li className="list-inline-item">
658 class="btn btn-sm btn-link btn-animate text-muted"
659 onClick={linkEvent(this, this.handleModLock)}
667 class={`icon icon-inline ${
668 post.locked && 'text-danger'
671 <use xlinkHref="#icon-lock"></use>
675 <li className="list-inline-item">
677 class="btn btn-sm btn-link btn-animate text-muted"
678 onClick={linkEvent(this, this.handleModSticky)}
686 class={`icon icon-inline ${
687 post.stickied && 'text-success'
690 <use xlinkHref="#icon-pin"></use>
696 {/* Mods can ban from community, and appoint as mods to community */}
697 {(this.canMod || this.canAdmin) && (
698 <li className="list-inline-item">
704 this.handleModRemoveShow
714 this.handleModRemoveSubmit
725 <li className="list-inline-item">
726 {!post.banned_from_community ? (
731 this.handleModBanFromCommunityShow
741 this.handleModBanFromCommunitySubmit
749 {!post.banned_from_community && (
750 <li className="list-inline-item">
755 this.handleAddModToCommunity
759 ? i18n.t('remove_as_mod')
760 : i18n.t('appoint_as_mod')}
766 {/* Community creators and admins can transfer community to another mod */}
767 {(this.amCommunityCreator || this.canAdmin) &&
769 <li className="list-inline-item">
770 {!this.state.showConfirmTransferCommunity ? (
775 this.handleShowConfirmTransferCommunity
778 {i18n.t('transfer_community')}
782 <span class="d-inline-block mr-1">
783 {i18n.t('are_you_sure')}
786 class="pointer d-inline-block mr-1"
789 this.handleTransferCommunity
795 class="pointer d-inline-block"
799 .handleCancelShowConfirmTransferCommunity
808 {/* Admins can ban from all, and appoint other admins */}
812 <li className="list-inline-item">
818 this.handleModBanShow
821 {i18n.t('ban_from_site')}
828 this.handleModBanSubmit
831 {i18n.t('unban_from_site')}
837 <li className="list-inline-item">
840 onClick={linkEvent(this, this.handleAddAdmin)}
843 ? i18n.t('remove_as_admin')
844 : i18n.t('appoint_as_admin')}
850 {/* Site Creator can transfer to another admin */}
851 {this.amSiteCreator && this.isAdmin && (
852 <li className="list-inline-item">
853 {!this.state.showConfirmTransferSite ? (
858 this.handleShowConfirmTransferSite
861 {i18n.t('transfer_site')}
865 <span class="d-inline-block mr-1">
866 {i18n.t('are_you_sure')}
869 class="pointer d-inline-block mr-1"
872 this.handleTransferSite
878 class="pointer d-inline-block"
881 this.handleCancelShowConfirmTransferSite
895 {this.state.showRemoveDialog && (
898 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
902 class="form-control mr-2"
903 placeholder={i18n.t('reason')}
904 value={this.state.removeReason}
905 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
907 <button type="submit" class="btn btn-secondary">
908 {i18n.t('remove_post')}
912 {this.state.showBanDialog && (
913 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
914 <div class="form-group row">
915 <label class="col-form-label" htmlFor="post-listing-reason">
920 id="post-listing-reason"
921 class="form-control mr-2"
922 placeholder={i18n.t('reason')}
923 value={this.state.banReason}
924 onInput={linkEvent(this, this.handleModBanReasonChange)}
927 {/* TODO hold off on expires until later */}
928 {/* <div class="form-group row"> */}
929 {/* <label class="col-form-label">Expires</label> */}
930 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
932 <div class="form-group row">
933 <button type="submit" class="btn btn-secondary">
934 {i18n.t('ban')} {post.creator_name}
946 private get myPost(): boolean {
948 UserService.Instance.user &&
949 this.props.post.creator_id == UserService.Instance.user.id
953 get isMod(): boolean {
955 this.props.moderators &&
957 this.props.moderators.map(m => m.user_id),
958 this.props.post.creator_id
963 get isAdmin(): boolean {
967 this.props.admins.map(a => a.id),
968 this.props.post.creator_id
973 get canMod(): boolean {
974 if (this.props.admins && this.props.moderators) {
975 let adminsThenMods = this.props.admins
977 .concat(this.props.moderators.map(m => m.user_id));
980 UserService.Instance.user,
982 this.props.post.creator_id
989 get canModOnSelf(): boolean {
990 if (this.props.admins && this.props.moderators) {
991 let adminsThenMods = this.props.admins
993 .concat(this.props.moderators.map(m => m.user_id));
996 UserService.Instance.user,
998 this.props.post.creator_id,
1006 get canAdmin(): boolean {
1008 this.props.admins &&
1010 UserService.Instance.user,
1011 this.props.admins.map(a => a.id),
1012 this.props.post.creator_id
1017 get amCommunityCreator(): boolean {
1019 this.props.moderators &&
1020 UserService.Instance.user &&
1021 this.props.post.creator_id != UserService.Instance.user.id &&
1022 UserService.Instance.user.id == this.props.moderators[0].user_id
1026 get amSiteCreator(): boolean {
1028 this.props.admins &&
1029 UserService.Instance.user &&
1030 this.props.post.creator_id != UserService.Instance.user.id &&
1031 UserService.Instance.user.id == this.props.admins[0].id
1035 handlePostLike(i: PostListing) {
1036 if (!UserService.Instance.user) {
1037 this.context.router.history.push(`/login`);
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 if (!UserService.Instance.user) {
1068 this.context.router.history.push(`/login`);
1071 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1073 if (i.state.my_vote == 1) {
1076 i.state.downvotes++;
1077 } else if (i.state.my_vote == -1) {
1078 i.state.downvotes--;
1081 i.state.downvotes++;
1085 i.state.my_vote = new_vote;
1087 let form: CreatePostLikeForm = {
1088 post_id: i.props.post.id,
1089 score: i.state.my_vote,
1092 WebSocketService.Instance.likePost(form);
1093 i.setState(i.state);
1097 handleEditClick(i: PostListing) {
1098 i.state.showEdit = true;
1099 i.setState(i.state);
1102 handleEditCancel() {
1103 this.state.showEdit = false;
1104 this.setState(this.state);
1107 // The actual editing is done in the recieve for post
1109 this.state.showEdit = false;
1110 this.setState(this.state);
1113 handleDeleteClick(i: PostListing) {
1114 let deleteForm: PostFormI = {
1115 body: i.props.post.body,
1116 community_id: i.props.post.community_id,
1117 name: i.props.post.name,
1118 url: i.props.post.url,
1119 edit_id: i.props.post.id,
1120 creator_id: i.props.post.creator_id,
1121 deleted: !i.props.post.deleted,
1122 nsfw: i.props.post.nsfw,
1125 WebSocketService.Instance.editPost(deleteForm);
1128 handleSavePostClick(i: PostListing) {
1129 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1130 let form: SavePostForm = {
1131 post_id: i.props.post.id,
1135 WebSocketService.Instance.savePost(form);
1138 get crossPostParams(): string {
1139 let params = `?title=${this.props.post.name}`;
1140 let post = this.props.post;
1143 params += `&url=${post.url}`;
1145 if (this.props.post.body) {
1146 params += `&body=${this.props.post.body}`;
1151 handleModRemoveShow(i: PostListing) {
1152 i.state.showRemoveDialog = true;
1153 i.setState(i.state);
1156 handleModRemoveReasonChange(i: PostListing, event: any) {
1157 i.state.removeReason = event.target.value;
1158 i.setState(i.state);
1161 handleModRemoveSubmit(i: PostListing) {
1162 event.preventDefault();
1163 let form: PostFormI = {
1164 name: i.props.post.name,
1165 community_id: i.props.post.community_id,
1166 edit_id: i.props.post.id,
1167 creator_id: i.props.post.creator_id,
1168 removed: !i.props.post.removed,
1169 reason: i.state.removeReason,
1170 nsfw: i.props.post.nsfw,
1173 WebSocketService.Instance.editPost(form);
1175 i.state.showRemoveDialog = false;
1176 i.setState(i.state);
1179 handleModLock(i: PostListing) {
1180 let form: PostFormI = {
1181 name: i.props.post.name,
1182 community_id: i.props.post.community_id,
1183 edit_id: i.props.post.id,
1184 creator_id: i.props.post.creator_id,
1185 nsfw: i.props.post.nsfw,
1186 locked: !i.props.post.locked,
1189 WebSocketService.Instance.editPost(form);
1192 handleModSticky(i: PostListing) {
1193 let form: PostFormI = {
1194 name: i.props.post.name,
1195 community_id: i.props.post.community_id,
1196 edit_id: i.props.post.id,
1197 creator_id: i.props.post.creator_id,
1198 nsfw: i.props.post.nsfw,
1199 stickied: !i.props.post.stickied,
1202 WebSocketService.Instance.editPost(form);
1205 handleModBanFromCommunityShow(i: PostListing) {
1206 i.state.showBanDialog = true;
1207 i.state.banType = BanType.Community;
1208 i.setState(i.state);
1211 handleModBanShow(i: PostListing) {
1212 i.state.showBanDialog = true;
1213 i.state.banType = BanType.Site;
1214 i.setState(i.state);
1217 handleModBanReasonChange(i: PostListing, event: any) {
1218 i.state.banReason = event.target.value;
1219 i.setState(i.state);
1222 handleModBanExpiresChange(i: PostListing, event: any) {
1223 i.state.banExpires = event.target.value;
1224 i.setState(i.state);
1227 handleModBanFromCommunitySubmit(i: PostListing) {
1228 i.state.banType = BanType.Community;
1229 i.setState(i.state);
1230 i.handleModBanBothSubmit(i);
1233 handleModBanSubmit(i: PostListing) {
1234 i.state.banType = BanType.Site;
1235 i.setState(i.state);
1236 i.handleModBanBothSubmit(i);
1239 handleModBanBothSubmit(i: PostListing) {
1240 event.preventDefault();
1242 if (i.state.banType == BanType.Community) {
1243 let form: BanFromCommunityForm = {
1244 user_id: i.props.post.creator_id,
1245 community_id: i.props.post.community_id,
1246 ban: !i.props.post.banned_from_community,
1247 reason: i.state.banReason,
1248 expires: getUnixTime(i.state.banExpires),
1250 WebSocketService.Instance.banFromCommunity(form);
1252 let form: BanUserForm = {
1253 user_id: i.props.post.creator_id,
1254 ban: !i.props.post.banned,
1255 reason: i.state.banReason,
1256 expires: getUnixTime(i.state.banExpires),
1258 WebSocketService.Instance.banUser(form);
1261 i.state.showBanDialog = false;
1262 i.setState(i.state);
1265 handleAddModToCommunity(i: PostListing) {
1266 let form: AddModToCommunityForm = {
1267 user_id: i.props.post.creator_id,
1268 community_id: i.props.post.community_id,
1271 WebSocketService.Instance.addModToCommunity(form);
1272 i.setState(i.state);
1275 handleAddAdmin(i: PostListing) {
1276 let form: AddAdminForm = {
1277 user_id: i.props.post.creator_id,
1280 WebSocketService.Instance.addAdmin(form);
1281 i.setState(i.state);
1284 handleShowConfirmTransferCommunity(i: PostListing) {
1285 i.state.showConfirmTransferCommunity = true;
1286 i.setState(i.state);
1289 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1290 i.state.showConfirmTransferCommunity = false;
1291 i.setState(i.state);
1294 handleTransferCommunity(i: PostListing) {
1295 let form: TransferCommunityForm = {
1296 community_id: i.props.post.community_id,
1297 user_id: i.props.post.creator_id,
1299 WebSocketService.Instance.transferCommunity(form);
1300 i.state.showConfirmTransferCommunity = false;
1301 i.setState(i.state);
1304 handleShowConfirmTransferSite(i: PostListing) {
1305 i.state.showConfirmTransferSite = true;
1306 i.setState(i.state);
1309 handleCancelShowConfirmTransferSite(i: PostListing) {
1310 i.state.showConfirmTransferSite = false;
1311 i.setState(i.state);
1314 handleTransferSite(i: PostListing) {
1315 let form: TransferSiteForm = {
1316 user_id: i.props.post.creator_id,
1318 WebSocketService.Instance.transferSite(form);
1319 i.state.showConfirmTransferSite = false;
1320 i.setState(i.state);
1323 handleImageExpandClick(i: PostListing) {
1324 i.state.imageExpanded = !i.state.imageExpanded;
1325 i.setState(i.state);
1328 handleViewSource(i: PostListing) {
1329 i.state.viewSource = !i.state.viewSource;
1330 i.setState(i.state);
1333 handleShowAdvanced(i: PostListing) {
1334 i.state.showAdvanced = !i.state.showAdvanced;
1335 i.setState(i.state);
1339 get pointsTippy(): string {
1340 let points = i18n.t('number_of_points', {
1341 count: this.state.score,
1344 let upvotes = i18n.t('number_of_upvotes', {
1345 count: this.state.upvotes,
1348 let downvotes = i18n.t('number_of_downvotes', {
1349 count: this.state.downvotes,
1352 return `${points} • ${upvotes} • ${downvotes}`;