1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { WebSocketService, UserService } from '../services';
17 AddModToCommunityForm,
20 TransferCommunityForm,
21 } from '../interfaces';
22 import { MomentTime } from './moment-time';
23 import { PostForm } from './post-form';
24 import { IFramelyCard } from './iframely-card';
25 import { UserListing } from './user-listing';
26 import { CommunityLink } from './community-link';
40 import { i18n } from '../i18next';
42 interface PostListingState {
44 showRemoveDialog: boolean;
46 showBanDialog: boolean;
50 showConfirmTransferSite: boolean;
51 showConfirmTransferCommunity: boolean;
52 imageExpanded: boolean;
54 showAdvanced: boolean;
61 interface PostListingProps {
63 showCommunity?: boolean;
65 moderators?: Array<CommunityUser>;
66 admins?: Array<UserView>;
67 enableDownvotes: boolean;
71 export class PostListing extends Component<PostListingProps, PostListingState> {
72 private emptyState: PostListingState = {
74 showRemoveDialog: false,
79 banType: BanType.Community,
80 showConfirmTransferSite: false,
81 showConfirmTransferCommunity: false,
85 my_vote: this.props.post.my_vote,
86 score: this.props.post.score,
87 upvotes: this.props.post.upvotes,
88 downvotes: this.props.post.downvotes,
91 constructor(props: any, context: any) {
92 super(props, context);
94 this.state = this.emptyState;
95 this.handlePostLike = this.handlePostLike.bind(this);
96 this.handlePostDisLike = this.handlePostDisLike.bind(this);
97 this.handleEditPost = this.handleEditPost.bind(this);
98 this.handleEditCancel = this.handleEditCancel.bind(this);
101 componentWillReceiveProps(nextProps: PostListingProps) {
102 this.state.my_vote = nextProps.post.my_vote;
103 this.state.upvotes = nextProps.post.upvotes;
104 this.state.downvotes = nextProps.post.downvotes;
105 this.state.score = nextProps.post.score;
106 if (this.props.post.id !== nextProps.post.id) {
107 this.state.imageExpanded = false;
109 this.setState(this.state);
115 {!this.state.showEdit ? (
123 post={this.props.post}
124 onEdit={this.handleEditPost}
125 onCancel={this.handleEditCancel}
126 enableNsfw={this.props.enableNsfw}
127 enableDownvotes={this.props.enableDownvotes}
139 {this.props.post.url &&
140 this.props.showBody &&
141 this.props.post.embed_title && (
142 <IFramelyCard post={this.props.post} />
144 {this.props.showBody && this.props.post.body && (
146 {this.state.viewSource ? (
147 <pre>{this.props.post.body}</pre>
151 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
161 imgThumb(src: string) {
162 let post = this.props.post;
165 className={`img-fluid thumbnail rounded ${
166 (post.nsfw || post.community_nsfw) && 'img-blur'
173 getImage(thumbnail: boolean = false) {
174 let post = this.props.post;
175 if (isImage(post.url)) {
176 if (post.url.includes('pictrs')) {
177 return pictrsImage(post.url, thumbnail);
178 } else if (post.thumbnail_url) {
179 return pictrsImage(post.thumbnail_url, thumbnail);
183 } else if (post.thumbnail_url) {
184 return pictrsImage(post.thumbnail_url, thumbnail);
189 let post = this.props.post;
191 if (isImage(post.url)) {
194 class="text-body pointer"
195 data-tippy-content={i18n.t('expand_here')}
196 onClick={linkEvent(this, this.handleImageExpandClick)}
198 {this.imgThumb(this.getImage(true))}
199 <svg class="icon mini-overlay">
200 <use xlinkHref="#icon-image"></use>
204 } else if (post.thumbnail_url) {
207 className="text-body"
213 {this.imgThumb(this.getImage(true))}
214 <svg class="icon mini-overlay">
215 <use xlinkHref="#icon-external-link"></use>
219 } else if (post.url) {
220 if (isVideo(post.url)) {
222 <div class="embed-responsive embed-responsive-16by9">
228 class="embed-responsive-item"
230 <source src={post.url} type="video/mp4" />
237 className="text-body"
243 <div class="thumbnail rounded bg-light d-flex justify-content-center">
244 <svg class="icon d-flex align-items-center">
245 <use xlinkHref="#icon-external-link"></use>
254 className="text-body"
255 to={`/post/${post.id}`}
256 title={i18n.t('comments')}
258 <div class="thumbnail rounded bg-light d-flex justify-content-center">
259 <svg class="icon d-flex align-items-center">
260 <use xlinkHref="#icon-message-square"></use>
269 let post = this.props.post;
272 <div className={`vote-bar col-1 pr-0 small text-center`}>
274 className={`btn-animate btn btn-link p-0 ${
275 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
277 onClick={linkEvent(this, this.handlePostLike)}
278 data-tippy-content={i18n.t('upvote')}
280 <svg class="icon upvote">
281 <use xlinkHref="#icon-arrow-up1"></use>
285 class={`unselectable pointer font-weight-bold text-muted px-1`}
286 data-tippy-content={this.pointsTippy}
290 {this.props.enableDownvotes && (
292 className={`btn-animate btn btn-link p-0 ${
293 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
295 onClick={linkEvent(this, this.handlePostDisLike)}
296 data-tippy-content={i18n.t('downvote')}
298 <svg class="icon downvote">
299 <use xlinkHref="#icon-arrow-down1"></use>
304 {!this.state.imageExpanded && (
305 <div class="col-3 col-sm-2 pr-0">
306 <div class="position-relative">{this.thumbnail()}</div>
310 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
313 <div className="col-12">
314 <div className="post-title">
315 <h5 className="mb-0 d-inline">
316 {this.props.showBody && post.url ? (
318 className="text-body"
328 className="text-body"
329 to={`/post/${post.id}`}
330 title={i18n.t('comments')}
336 {post.url && !(hostname(post.url) == window.location.hostname) && (
337 <small class="d-inline-block">
339 className="ml-2 text-muted font-italic"
346 <svg class="ml-1 icon icon-inline">
347 <use xlinkHref="#icon-external-link"></use>
352 {(isImage(post.url) || this.props.post.thumbnail_url) && (
354 {!this.state.imageExpanded ? (
356 class="text-monospace unselectable pointer ml-2 text-muted small"
357 data-tippy-content={i18n.t('expand_here')}
358 onClick={linkEvent(this, this.handleImageExpandClick)}
360 <svg class="icon icon-inline">
361 <use xlinkHref="#icon-plus-square"></use>
367 class="text-monospace unselectable pointer ml-2 text-muted small"
368 onClick={linkEvent(this, this.handleImageExpandClick)}
370 <svg class="icon icon-inline">
371 <use xlinkHref="#icon-minus-square"></use>
379 this.handleImageExpandClick
383 class="img-fluid img-expanded"
384 src={this.getImage()}
393 <small className="ml-2 text-muted font-italic">
399 className="unselectable pointer ml-2 text-muted font-italic"
400 data-tippy-content={i18n.t('deleted')}
402 <svg class={`icon icon-inline text-danger`}>
403 <use xlinkHref="#icon-trash"></use>
409 className="unselectable pointer ml-2 text-muted font-italic"
410 data-tippy-content={i18n.t('locked')}
412 <svg class={`icon icon-inline text-danger`}>
413 <use xlinkHref="#icon-lock"></use>
419 className="unselectable pointer ml-2 text-muted font-italic"
420 data-tippy-content={i18n.t('stickied')}
422 <svg class={`icon icon-inline text-success`}>
423 <use xlinkHref="#icon-pin"></use>
428 <small className="ml-2 text-muted font-italic">
436 <div className="details col-12">
437 <ul class="list-inline mb-0 text-muted small">
438 <li className="list-inline-item">
439 <span>{i18n.t('by')} </span>
442 name: post.creator_name,
443 avatar: post.creator_avatar,
445 local: post.creator_local,
446 actor_id: post.creator_actor_id,
447 published: post.creator_published,
452 <span className="mx-1 badge badge-light">
457 <span className="mx-1 badge badge-light">
461 {(post.banned_from_community || post.banned) && (
462 <span className="mx-1 badge badge-danger">
466 {this.props.showCommunity && (
468 <span> {i18n.t('to')} </span>
471 name: post.community_name,
472 id: post.community_id,
473 local: post.community_local,
474 actor_id: post.community_actor_id,
480 <li className="list-inline-item">•</li>
481 <li className="list-inline-item">
483 <MomentTime data={post} />
488 <li className="list-inline-item">•</li>
489 <li className="list-inline-item">
490 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
492 className="text-muted"
493 data-tippy-content={md.render(previewLines(post.body))}
494 data-tippy-allowHtml={true}
495 to={`/post/${post.id}`}
497 <svg class="mr-1 icon icon-inline">
498 <use xlinkHref="#icon-book-open"></use>
504 <li className="list-inline-item">•</li>
505 {this.state.upvotes !== this.state.score && (
508 class="unselectable pointer mr-2"
509 data-tippy-content={this.pointsTippy}
511 <li className="list-inline-item">
512 <span className="text-muted">
513 <svg class="small icon icon-inline mr-1">
514 <use xlinkHref="#icon-arrow-up"></use>
519 <li className="list-inline-item">
520 <span className="text-muted">
521 <svg class="small icon icon-inline mr-1">
522 <use xlinkHref="#icon-arrow-down"></use>
524 {this.state.downvotes}
528 <li className="list-inline-item">•</li>
531 <li className="list-inline-item">
533 className="text-muted"
534 title={i18n.t('number_of_comments', {
535 count: post.number_of_comments,
537 to={`/post/${post.id}`}
539 <svg class="mr-1 icon icon-inline">
540 <use xlinkHref="#icon-message-square"></use>
542 {post.number_of_comments}
546 {this.props.post.duplicates && (
547 <ul class="list-inline mb-1 small text-muted">
549 <li className="list-inline-item mr-2">
550 {i18n.t('cross_posted_to')}
552 {this.props.post.duplicates.map(post => (
553 <li className="list-inline-item mr-2">
554 <Link to={`/post/${post.id}`}>
555 {post.community_name}
562 <ul class="list-inline mb-1 text-muted font-weight-bold">
563 {UserService.Instance.user && (
565 {this.props.showBody && (
567 <li className="list-inline-item">
569 class="btn btn-link btn-animate text-muted"
570 onClick={linkEvent(this, this.handleSavePostClick)}
572 post.saved ? i18n.t('unsave') : i18n.t('save')
576 class={`icon icon-inline ${
577 post.saved && 'text-warning'
580 <use xlinkHref="#icon-star"></use>
584 <li className="list-inline-item">
586 class="btn btn-link btn-animate text-muted"
587 to={`/create_post${this.crossPostParams}`}
588 title={i18n.t('cross_post')}
590 <svg class="icon icon-inline">
591 <use xlinkHref="#icon-copy"></use>
597 {this.myPost && this.props.showBody && (
599 <li className="list-inline-item">
601 class="btn btn-link btn-animate text-muted"
602 onClick={linkEvent(this, this.handleEditClick)}
603 data-tippy-content={i18n.t('edit')}
605 <svg class="icon icon-inline">
606 <use xlinkHref="#icon-edit"></use>
610 <li className="list-inline-item">
612 class="btn btn-link btn-animate text-muted"
613 onClick={linkEvent(this, this.handleDeleteClick)}
621 class={`icon icon-inline ${
622 post.deleted && 'text-danger'
625 <use xlinkHref="#icon-trash"></use>
632 {!this.state.showAdvanced && this.props.showBody ? (
633 <li className="list-inline-item">
635 class="btn btn-link btn-animate text-muted"
636 onClick={linkEvent(this, this.handleShowAdvanced)}
637 data-tippy-content={i18n.t('more')}
639 <svg class="icon icon-inline">
640 <use xlinkHref="#icon-more-vertical"></use>
646 {this.props.showBody && post.body && (
647 <li className="list-inline-item">
649 class="btn btn-link btn-animate text-muted"
650 onClick={linkEvent(this, this.handleViewSource)}
651 data-tippy-content={i18n.t('view_source')}
654 class={`icon icon-inline ${
655 this.state.viewSource && 'text-success'
658 <use xlinkHref="#icon-file-text"></use>
663 {this.canModOnSelf && (
665 <li className="list-inline-item">
667 class="btn btn-link btn-animate text-muted"
668 onClick={linkEvent(this, this.handleModLock)}
676 class={`icon icon-inline ${
677 post.locked && 'text-danger'
680 <use xlinkHref="#icon-lock"></use>
684 <li className="list-inline-item">
686 class="btn btn-link btn-animate text-muted"
687 onClick={linkEvent(this, this.handleModSticky)}
695 class={`icon icon-inline ${
696 post.stickied && 'text-success'
699 <use xlinkHref="#icon-pin"></use>
705 {/* Mods can ban from community, and appoint as mods to community */}
706 {(this.canMod || this.canAdmin) && (
707 <li className="list-inline-item">
713 this.handleModRemoveShow
723 this.handleModRemoveSubmit
734 <li className="list-inline-item">
735 {!post.banned_from_community ? (
740 this.handleModBanFromCommunityShow
750 this.handleModBanFromCommunitySubmit
758 {!post.banned_from_community && (
759 <li className="list-inline-item">
764 this.handleAddModToCommunity
768 ? i18n.t('remove_as_mod')
769 : i18n.t('appoint_as_mod')}
775 {/* Community creators and admins can transfer community to another mod */}
776 {(this.amCommunityCreator || this.canAdmin) &&
778 <li className="list-inline-item">
779 {!this.state.showConfirmTransferCommunity ? (
784 this.handleShowConfirmTransferCommunity
787 {i18n.t('transfer_community')}
791 <span class="d-inline-block mr-1">
792 {i18n.t('are_you_sure')}
795 class="pointer d-inline-block mr-1"
798 this.handleTransferCommunity
804 class="pointer d-inline-block"
808 .handleCancelShowConfirmTransferCommunity
817 {/* Admins can ban from all, and appoint other admins */}
821 <li className="list-inline-item">
827 this.handleModBanShow
830 {i18n.t('ban_from_site')}
837 this.handleModBanSubmit
840 {i18n.t('unban_from_site')}
846 <li className="list-inline-item">
849 onClick={linkEvent(this, this.handleAddAdmin)}
852 ? i18n.t('remove_as_admin')
853 : i18n.t('appoint_as_admin')}
859 {/* Site Creator can transfer to another admin */}
860 {this.amSiteCreator && this.isAdmin && (
861 <li className="list-inline-item">
862 {!this.state.showConfirmTransferSite ? (
867 this.handleShowConfirmTransferSite
870 {i18n.t('transfer_site')}
874 <span class="d-inline-block mr-1">
875 {i18n.t('are_you_sure')}
878 class="pointer d-inline-block mr-1"
881 this.handleTransferSite
887 class="pointer d-inline-block"
890 this.handleCancelShowConfirmTransferSite
904 {this.state.showRemoveDialog && (
907 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
911 class="form-control mr-2"
912 placeholder={i18n.t('reason')}
913 value={this.state.removeReason}
914 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
916 <button type="submit" class="btn btn-secondary">
917 {i18n.t('remove_post')}
921 {this.state.showBanDialog && (
922 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
923 <div class="form-group row">
924 <label class="col-form-label" htmlFor="post-listing-reason">
929 id="post-listing-reason"
930 class="form-control mr-2"
931 placeholder={i18n.t('reason')}
932 value={this.state.banReason}
933 onInput={linkEvent(this, this.handleModBanReasonChange)}
936 {/* TODO hold off on expires until later */}
937 {/* <div class="form-group row"> */}
938 {/* <label class="col-form-label">Expires</label> */}
939 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
941 <div class="form-group row">
942 <button type="submit" class="btn btn-secondary">
943 {i18n.t('ban')} {post.creator_name}
955 private get myPost(): boolean {
957 UserService.Instance.user &&
958 this.props.post.creator_id == UserService.Instance.user.id
962 get isMod(): boolean {
964 this.props.moderators &&
966 this.props.moderators.map(m => m.user_id),
967 this.props.post.creator_id
972 get isAdmin(): boolean {
976 this.props.admins.map(a => a.id),
977 this.props.post.creator_id
982 get canMod(): boolean {
983 if (this.props.admins && this.props.moderators) {
984 let adminsThenMods = this.props.admins
986 .concat(this.props.moderators.map(m => m.user_id));
989 UserService.Instance.user,
991 this.props.post.creator_id
998 get canModOnSelf(): boolean {
999 if (this.props.admins && this.props.moderators) {
1000 let adminsThenMods = this.props.admins
1002 .concat(this.props.moderators.map(m => m.user_id));
1005 UserService.Instance.user,
1007 this.props.post.creator_id,
1015 get canAdmin(): boolean {
1017 this.props.admins &&
1019 UserService.Instance.user,
1020 this.props.admins.map(a => a.id),
1021 this.props.post.creator_id
1026 get amCommunityCreator(): boolean {
1028 this.props.moderators &&
1029 UserService.Instance.user &&
1030 this.props.post.creator_id != UserService.Instance.user.id &&
1031 UserService.Instance.user.id == this.props.moderators[0].user_id
1035 get amSiteCreator(): boolean {
1037 this.props.admins &&
1038 UserService.Instance.user &&
1039 this.props.post.creator_id != UserService.Instance.user.id &&
1040 UserService.Instance.user.id == this.props.admins[0].id
1044 handlePostLike(i: PostListing) {
1045 if (!UserService.Instance.user) {
1046 this.context.router.history.push(`/login`);
1049 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1051 if (i.state.my_vote == 1) {
1054 } else if (i.state.my_vote == -1) {
1055 i.state.downvotes--;
1063 i.state.my_vote = new_vote;
1065 let form: CreatePostLikeForm = {
1066 post_id: i.props.post.id,
1067 score: i.state.my_vote,
1070 WebSocketService.Instance.likePost(form);
1071 i.setState(i.state);
1075 handlePostDisLike(i: PostListing) {
1076 if (!UserService.Instance.user) {
1077 this.context.router.history.push(`/login`);
1080 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1082 if (i.state.my_vote == 1) {
1085 i.state.downvotes++;
1086 } else if (i.state.my_vote == -1) {
1087 i.state.downvotes--;
1090 i.state.downvotes++;
1094 i.state.my_vote = new_vote;
1096 let form: CreatePostLikeForm = {
1097 post_id: i.props.post.id,
1098 score: i.state.my_vote,
1101 WebSocketService.Instance.likePost(form);
1102 i.setState(i.state);
1106 handleEditClick(i: PostListing) {
1107 i.state.showEdit = true;
1108 i.setState(i.state);
1111 handleEditCancel() {
1112 this.state.showEdit = false;
1113 this.setState(this.state);
1116 // The actual editing is done in the recieve for post
1118 this.state.showEdit = false;
1119 this.setState(this.state);
1122 handleDeleteClick(i: PostListing) {
1123 let deleteForm: DeletePostForm = {
1124 edit_id: i.props.post.id,
1125 deleted: !i.props.post.deleted,
1128 WebSocketService.Instance.deletePost(deleteForm);
1131 handleSavePostClick(i: PostListing) {
1132 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1133 let form: SavePostForm = {
1134 post_id: i.props.post.id,
1138 WebSocketService.Instance.savePost(form);
1141 get crossPostParams(): string {
1142 let params = `?title=${this.props.post.name}`;
1143 let post = this.props.post;
1146 params += `&url=${post.url}`;
1148 if (this.props.post.body) {
1149 params += `&body=${this.props.post.body}`;
1154 handleModRemoveShow(i: PostListing) {
1155 i.state.showRemoveDialog = true;
1156 i.setState(i.state);
1159 handleModRemoveReasonChange(i: PostListing, event: any) {
1160 i.state.removeReason = event.target.value;
1161 i.setState(i.state);
1164 handleModRemoveSubmit(i: PostListing) {
1165 event.preventDefault();
1166 let form: RemovePostForm = {
1167 edit_id: i.props.post.id,
1168 removed: !i.props.post.removed,
1169 reason: i.state.removeReason,
1172 WebSocketService.Instance.removePost(form);
1174 i.state.showRemoveDialog = false;
1175 i.setState(i.state);
1178 handleModLock(i: PostListing) {
1179 let form: LockPostForm = {
1180 edit_id: i.props.post.id,
1181 locked: !i.props.post.locked,
1184 WebSocketService.Instance.lockPost(form);
1187 handleModSticky(i: PostListing) {
1188 let form: StickyPostForm = {
1189 edit_id: i.props.post.id,
1190 stickied: !i.props.post.stickied,
1193 WebSocketService.Instance.stickyPost(form);
1196 handleModBanFromCommunityShow(i: PostListing) {
1197 i.state.showBanDialog = true;
1198 i.state.banType = BanType.Community;
1199 i.setState(i.state);
1202 handleModBanShow(i: PostListing) {
1203 i.state.showBanDialog = true;
1204 i.state.banType = BanType.Site;
1205 i.setState(i.state);
1208 handleModBanReasonChange(i: PostListing, event: any) {
1209 i.state.banReason = event.target.value;
1210 i.setState(i.state);
1213 handleModBanExpiresChange(i: PostListing, event: any) {
1214 i.state.banExpires = event.target.value;
1215 i.setState(i.state);
1218 handleModBanFromCommunitySubmit(i: PostListing) {
1219 i.state.banType = BanType.Community;
1220 i.setState(i.state);
1221 i.handleModBanBothSubmit(i);
1224 handleModBanSubmit(i: PostListing) {
1225 i.state.banType = BanType.Site;
1226 i.setState(i.state);
1227 i.handleModBanBothSubmit(i);
1230 handleModBanBothSubmit(i: PostListing) {
1231 event.preventDefault();
1233 if (i.state.banType == BanType.Community) {
1234 let form: BanFromCommunityForm = {
1235 user_id: i.props.post.creator_id,
1236 community_id: i.props.post.community_id,
1237 ban: !i.props.post.banned_from_community,
1238 reason: i.state.banReason,
1239 expires: getUnixTime(i.state.banExpires),
1241 WebSocketService.Instance.banFromCommunity(form);
1243 let form: BanUserForm = {
1244 user_id: i.props.post.creator_id,
1245 ban: !i.props.post.banned,
1246 reason: i.state.banReason,
1247 expires: getUnixTime(i.state.banExpires),
1249 WebSocketService.Instance.banUser(form);
1252 i.state.showBanDialog = false;
1253 i.setState(i.state);
1256 handleAddModToCommunity(i: PostListing) {
1257 let form: AddModToCommunityForm = {
1258 user_id: i.props.post.creator_id,
1259 community_id: i.props.post.community_id,
1262 WebSocketService.Instance.addModToCommunity(form);
1263 i.setState(i.state);
1266 handleAddAdmin(i: PostListing) {
1267 let form: AddAdminForm = {
1268 user_id: i.props.post.creator_id,
1271 WebSocketService.Instance.addAdmin(form);
1272 i.setState(i.state);
1275 handleShowConfirmTransferCommunity(i: PostListing) {
1276 i.state.showConfirmTransferCommunity = true;
1277 i.setState(i.state);
1280 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1281 i.state.showConfirmTransferCommunity = false;
1282 i.setState(i.state);
1285 handleTransferCommunity(i: PostListing) {
1286 let form: TransferCommunityForm = {
1287 community_id: i.props.post.community_id,
1288 user_id: i.props.post.creator_id,
1290 WebSocketService.Instance.transferCommunity(form);
1291 i.state.showConfirmTransferCommunity = false;
1292 i.setState(i.state);
1295 handleShowConfirmTransferSite(i: PostListing) {
1296 i.state.showConfirmTransferSite = true;
1297 i.setState(i.state);
1300 handleCancelShowConfirmTransferSite(i: PostListing) {
1301 i.state.showConfirmTransferSite = false;
1302 i.setState(i.state);
1305 handleTransferSite(i: PostListing) {
1306 let form: TransferSiteForm = {
1307 user_id: i.props.post.creator_id,
1309 WebSocketService.Instance.transferSite(form);
1310 i.state.showConfirmTransferSite = false;
1311 i.setState(i.state);
1314 handleImageExpandClick(i: PostListing) {
1315 i.state.imageExpanded = !i.state.imageExpanded;
1316 i.setState(i.state);
1319 handleViewSource(i: PostListing) {
1320 i.state.viewSource = !i.state.viewSource;
1321 i.setState(i.state);
1324 handleShowAdvanced(i: PostListing) {
1325 i.state.showAdvanced = !i.state.showAdvanced;
1326 i.setState(i.state);
1330 get pointsTippy(): string {
1331 let points = i18n.t('number_of_points', {
1332 count: this.state.score,
1335 let upvotes = i18n.t('number_of_upvotes', {
1336 count: this.state.upvotes,
1339 let downvotes = i18n.t('number_of_downvotes', {
1340 count: this.state.downvotes,
1343 return `${points} • ${upvotes} • ${downvotes}`;