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';
39 interface PostListingState {
41 showRemoveDialog: boolean;
43 showBanDialog: boolean;
47 showConfirmTransferSite: boolean;
48 showConfirmTransferCommunity: boolean;
49 imageExpanded: boolean;
51 showAdvanced: boolean;
58 interface PostListingProps {
60 showCommunity?: boolean;
62 moderators?: Array<CommunityUser>;
63 admins?: Array<UserView>;
66 export class PostListing extends Component<PostListingProps, PostListingState> {
67 private emptyState: PostListingState = {
69 showRemoveDialog: false,
74 banType: BanType.Community,
75 showConfirmTransferSite: false,
76 showConfirmTransferCommunity: false,
80 my_vote: this.props.post.my_vote,
81 score: this.props.post.score,
82 upvotes: this.props.post.upvotes,
83 downvotes: this.props.post.downvotes,
86 constructor(props: any, context: any) {
87 super(props, context);
89 this.state = this.emptyState;
90 this.handlePostLike = this.handlePostLike.bind(this);
91 this.handlePostDisLike = this.handlePostDisLike.bind(this);
92 this.handleEditPost = this.handleEditPost.bind(this);
93 this.handleEditCancel = this.handleEditCancel.bind(this);
96 componentWillReceiveProps(nextProps: PostListingProps) {
97 this.state.my_vote = nextProps.post.my_vote;
98 this.state.upvotes = nextProps.post.upvotes;
99 this.state.downvotes = nextProps.post.downvotes;
100 this.state.score = nextProps.post.score;
101 this.setState(this.state);
107 {!this.state.showEdit ? (
115 post={this.props.post}
116 onEdit={this.handleEditPost}
117 onCancel={this.handleEditCancel}
129 {this.props.post.url &&
130 this.props.showBody &&
131 this.props.post.embed_title && (
132 <IFramelyCard post={this.props.post} />
134 {this.props.showBody && this.props.post.body && (
136 {this.state.viewSource ? (
137 <pre>{this.props.post.body}</pre>
141 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
151 imgThumb(src: string) {
152 let post = this.props.post;
155 className={`img-fluid thumbnail rounded ${
156 (post.nsfw || post.community_nsfw) && 'img-blur'
163 getImage(thumbnail: boolean = false) {
164 let post = this.props.post;
165 if (isImage(post.url)) {
166 if (post.url.includes('pictrs')) {
167 return pictrsImage(post.url, thumbnail);
168 } else if (post.thumbnail_url) {
169 return pictrsImage(post.thumbnail_url, thumbnail);
173 } else if (post.thumbnail_url) {
174 return pictrsImage(post.thumbnail_url, thumbnail);
179 let post = this.props.post;
181 if (isImage(post.url)) {
184 class="text-body pointer"
185 data-tippy-content={i18n.t('expand_here')}
186 onClick={linkEvent(this, this.handleImageExpandClick)}
188 {this.imgThumb(this.getImage(true))}
189 <svg class="icon mini-overlay">
190 <use xlinkHref="#icon-image"></use>
194 } else if (post.thumbnail_url) {
197 className="text-body"
203 {this.imgThumb(this.getImage(true))}
204 <svg class="icon mini-overlay">
205 <use xlinkHref="#icon-external-link"></use>
209 } else if (post.url) {
210 if (isVideo(post.url)) {
212 <div class="embed-responsive embed-responsive-16by9">
218 class="embed-responsive-item"
220 <source src={post.url} type="video/mp4" />
227 className="text-body"
233 <svg class="icon thumbnail">
234 <use xlinkHref="#icon-external-link"></use>
242 className="text-body"
243 to={`/post/${post.id}`}
244 title={i18n.t('comments')}
246 <svg class="icon thumbnail">
247 <use xlinkHref="#icon-message-square"></use>
255 let post = this.props.post;
258 <div className={`vote-bar col-1 pr-0 small text-center`}>
260 className={`btn-animate btn btn-link p-0 ${
261 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
263 onClick={linkEvent(this, this.handlePostLike)}
264 data-tippy-content={i18n.t('upvote')}
266 <svg class="icon upvote">
267 <use xlinkHref="#icon-arrow-up1"></use>
271 class={`unselectable pointer font-weight-bold text-muted px-1`}
272 data-tippy-content={this.pointsTippy}
276 {WebSocketService.Instance.site.enable_downvotes && (
278 className={`btn-animate btn btn-link p-0 ${
279 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
281 onClick={linkEvent(this, this.handlePostDisLike)}
282 data-tippy-content={i18n.t('downvote')}
284 <svg class="icon downvote">
285 <use xlinkHref="#icon-arrow-down1"></use>
290 {!this.state.imageExpanded && (
291 <div class="col-3 col-sm-2 pr-0 mt-1">
292 <div class="position-relative">{this.thumbnail()}</div>
296 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
299 <div className="col-12">
300 <div className="post-title">
301 <h5 className="mb-0 d-inline">
302 {this.props.showBody && post.url ? (
304 className="text-body"
314 className="text-body"
315 to={`/post/${post.id}`}
316 title={i18n.t('comments')}
322 {post.url && !(hostname(post.url) == window.location.hostname) && (
323 <small class="d-inline-block">
325 className="ml-2 text-muted font-italic"
332 <svg class="ml-1 icon icon-inline">
333 <use xlinkHref="#icon-external-link"></use>
338 {(isImage(post.url) || this.props.post.thumbnail_url) && (
340 {!this.state.imageExpanded ? (
342 class="text-monospace unselectable pointer ml-2 text-muted small"
343 data-tippy-content={i18n.t('expand_here')}
344 onClick={linkEvent(this, this.handleImageExpandClick)}
346 <svg class="icon icon-inline">
347 <use xlinkHref="#icon-plus-square"></use>
353 class="text-monospace unselectable pointer ml-2 text-muted small"
354 onClick={linkEvent(this, this.handleImageExpandClick)}
356 <svg class="icon icon-inline">
357 <use xlinkHref="#icon-minus-square"></use>
365 this.handleImageExpandClick
369 class="img-fluid img-expanded"
370 src={this.getImage()}
379 <small className="ml-2 text-muted font-italic">
385 className="unselectable pointer ml-2 text-muted font-italic"
386 data-tippy-content={i18n.t('deleted')}
388 <svg class={`icon icon-inline text-danger`}>
389 <use xlinkHref="#icon-trash"></use>
395 className="unselectable pointer ml-2 text-muted font-italic"
396 data-tippy-content={i18n.t('locked')}
398 <svg class={`icon icon-inline text-danger`}>
399 <use xlinkHref="#icon-lock"></use>
405 className="unselectable pointer ml-2 text-muted font-italic"
406 data-tippy-content={i18n.t('stickied')}
408 <svg class={`icon icon-inline text-success`}>
409 <use xlinkHref="#icon-pin"></use>
414 <small className="ml-2 text-muted font-italic">
422 <div className="details col-12">
423 <ul class="list-inline mb-0 text-muted small">
424 <li className="list-inline-item">
425 <span>{i18n.t('by')} </span>
428 name: post.creator_name,
429 avatar: post.creator_avatar,
431 local: post.creator_local,
432 actor_id: post.creator_actor_id,
436 <span className="mx-1 badge badge-light">
441 <span className="mx-1 badge badge-light">
445 {(post.banned_from_community || post.banned) && (
446 <span className="mx-1 badge badge-danger">
450 {this.props.showCommunity && (
452 <span> {i18n.t('to')} </span>
455 name: post.community_name,
456 id: post.community_id,
457 local: post.community_local,
458 actor_id: post.community_actor_id,
464 <li className="list-inline-item">•</li>
465 <li className="list-inline-item">
467 <MomentTime data={post} />
472 <li className="list-inline-item">•</li>
473 <li className="list-inline-item">
474 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
476 className="text-muted"
477 data-tippy-content={md.render(previewLines(post.body))}
478 data-tippy-allowHtml={true}
479 to={`/post/${post.id}`}
481 <svg class="mr-1 icon icon-inline">
482 <use xlinkHref="#icon-book-open"></use>
488 <li className="list-inline-item">•</li>
489 {this.state.upvotes !== this.state.score && (
492 class="unselectable pointer mr-2"
493 data-tippy-content={this.pointsTippy}
495 <li className="list-inline-item">
496 <span className="text-muted">
497 <svg class="small icon icon-inline mr-1">
498 <use xlinkHref="#icon-arrow-up"></use>
503 <li className="list-inline-item">
504 <span className="text-muted">
505 <svg class="small icon icon-inline mr-1">
506 <use xlinkHref="#icon-arrow-down"></use>
508 {this.state.downvotes}
512 <li className="list-inline-item">•</li>
515 <li className="list-inline-item">
517 className="text-muted"
518 title={i18n.t('number_of_comments', {
519 count: post.number_of_comments,
521 to={`/post/${post.id}`}
523 <svg class="mr-1 icon icon-inline">
524 <use xlinkHref="#icon-message-square"></use>
526 {post.number_of_comments}
530 {this.props.post.duplicates && (
531 <ul class="list-inline mb-1 small text-muted">
533 <li className="list-inline-item mr-2">
534 {i18n.t('cross_posted_to')}
536 {this.props.post.duplicates.map(post => (
537 <li className="list-inline-item mr-2">
538 <Link to={`/post/${post.id}`}>
539 {post.community_name}
546 <ul class="list-inline mb-1 text-muted font-weight-bold">
547 {UserService.Instance.user && (
549 {this.props.showBody && (
551 <li className="list-inline-item">
553 class="btn btn-sm btn-link btn-animate text-muted"
554 onClick={linkEvent(this, this.handleSavePostClick)}
556 post.saved ? i18n.t('unsave') : i18n.t('save')
560 class={`icon icon-inline ${
561 post.saved && 'text-warning'
564 <use xlinkHref="#icon-star"></use>
568 <li className="list-inline-item">
570 class="btn btn-sm btn-link btn-animate text-muted"
571 to={`/create_post${this.crossPostParams}`}
572 title={i18n.t('cross_post')}
574 <svg class="icon icon-inline">
575 <use xlinkHref="#icon-copy"></use>
581 {this.myPost && this.props.showBody && (
583 <li className="list-inline-item">
585 class="btn btn-sm btn-link btn-animate text-muted"
586 onClick={linkEvent(this, this.handleEditClick)}
587 data-tippy-content={i18n.t('edit')}
589 <svg class="icon icon-inline">
590 <use xlinkHref="#icon-edit"></use>
594 <li className="list-inline-item">
596 class="btn btn-sm btn-link btn-animate text-muted"
597 onClick={linkEvent(this, this.handleDeleteClick)}
605 class={`icon icon-inline ${
606 post.deleted && 'text-danger'
609 <use xlinkHref="#icon-trash"></use>
616 {!this.state.showAdvanced && this.props.showBody ? (
617 <li className="list-inline-item">
619 class="btn btn-sm btn-link btn-animate text-muted"
620 onClick={linkEvent(this, this.handleShowAdvanced)}
621 data-tippy-content={i18n.t('more')}
623 <svg class="icon icon-inline">
624 <use xlinkHref="#icon-more-vertical"></use>
630 {this.props.showBody && post.body && (
631 <li className="list-inline-item">
633 class="btn btn-sm btn-link btn-animate text-muted"
634 onClick={linkEvent(this, this.handleViewSource)}
635 data-tippy-content={i18n.t('view_source')}
638 class={`icon icon-inline ${
639 this.state.viewSource && 'text-success'
642 <use xlinkHref="#icon-file-text"></use>
647 {this.canModOnSelf && (
649 <li className="list-inline-item">
651 class="btn btn-sm btn-link btn-animate text-muted"
652 onClick={linkEvent(this, this.handleModLock)}
660 class={`icon icon-inline ${
661 post.locked && 'text-danger'
664 <use xlinkHref="#icon-lock"></use>
668 <li className="list-inline-item">
670 class="btn btn-sm btn-link btn-animate text-muted"
671 onClick={linkEvent(this, this.handleModSticky)}
679 class={`icon icon-inline ${
680 post.stickied && 'text-success'
683 <use xlinkHref="#icon-pin"></use>
689 {/* Mods can ban from community, and appoint as mods to community */}
690 {(this.canMod || this.canAdmin) && (
691 <li className="list-inline-item">
697 this.handleModRemoveShow
707 this.handleModRemoveSubmit
718 <li className="list-inline-item">
719 {!post.banned_from_community ? (
724 this.handleModBanFromCommunityShow
734 this.handleModBanFromCommunitySubmit
742 {!post.banned_from_community && (
743 <li className="list-inline-item">
748 this.handleAddModToCommunity
752 ? i18n.t('remove_as_mod')
753 : i18n.t('appoint_as_mod')}
759 {/* Community creators and admins can transfer community to another mod */}
760 {(this.amCommunityCreator || this.canAdmin) &&
762 <li className="list-inline-item">
763 {!this.state.showConfirmTransferCommunity ? (
768 this.handleShowConfirmTransferCommunity
771 {i18n.t('transfer_community')}
775 <span class="d-inline-block mr-1">
776 {i18n.t('are_you_sure')}
779 class="pointer d-inline-block mr-1"
782 this.handleTransferCommunity
788 class="pointer d-inline-block"
792 .handleCancelShowConfirmTransferCommunity
801 {/* Admins can ban from all, and appoint other admins */}
805 <li className="list-inline-item">
811 this.handleModBanShow
814 {i18n.t('ban_from_site')}
821 this.handleModBanSubmit
824 {i18n.t('unban_from_site')}
830 <li className="list-inline-item">
833 onClick={linkEvent(this, this.handleAddAdmin)}
836 ? i18n.t('remove_as_admin')
837 : i18n.t('appoint_as_admin')}
843 {/* Site Creator can transfer to another admin */}
844 {this.amSiteCreator && this.isAdmin && (
845 <li className="list-inline-item">
846 {!this.state.showConfirmTransferSite ? (
851 this.handleShowConfirmTransferSite
854 {i18n.t('transfer_site')}
858 <span class="d-inline-block mr-1">
859 {i18n.t('are_you_sure')}
862 class="pointer d-inline-block mr-1"
865 this.handleTransferSite
871 class="pointer d-inline-block"
874 this.handleCancelShowConfirmTransferSite
888 {this.state.showRemoveDialog && (
891 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
895 class="form-control mr-2"
896 placeholder={i18n.t('reason')}
897 value={this.state.removeReason}
898 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
900 <button type="submit" class="btn btn-secondary">
901 {i18n.t('remove_post')}
905 {this.state.showBanDialog && (
906 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
907 <div class="form-group row">
908 <label class="col-form-label" htmlFor="post-listing-reason">
913 id="post-listing-reason"
914 class="form-control mr-2"
915 placeholder={i18n.t('reason')}
916 value={this.state.banReason}
917 onInput={linkEvent(this, this.handleModBanReasonChange)}
920 {/* TODO hold off on expires until later */}
921 {/* <div class="form-group row"> */}
922 {/* <label class="col-form-label">Expires</label> */}
923 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
925 <div class="form-group row">
926 <button type="submit" class="btn btn-secondary">
927 {i18n.t('ban')} {post.creator_name}
939 private get myPost(): boolean {
941 UserService.Instance.user &&
942 this.props.post.creator_id == UserService.Instance.user.id
946 get isMod(): boolean {
948 this.props.moderators &&
950 this.props.moderators.map(m => m.user_id),
951 this.props.post.creator_id
956 get isAdmin(): boolean {
960 this.props.admins.map(a => a.id),
961 this.props.post.creator_id
966 get canMod(): boolean {
967 if (this.props.admins && this.props.moderators) {
968 let adminsThenMods = this.props.admins
970 .concat(this.props.moderators.map(m => m.user_id));
973 UserService.Instance.user,
975 this.props.post.creator_id
982 get canModOnSelf(): 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,
999 get canAdmin(): boolean {
1001 this.props.admins &&
1003 UserService.Instance.user,
1004 this.props.admins.map(a => a.id),
1005 this.props.post.creator_id
1010 get amCommunityCreator(): boolean {
1012 this.props.moderators &&
1013 UserService.Instance.user &&
1014 this.props.post.creator_id != UserService.Instance.user.id &&
1015 UserService.Instance.user.id == this.props.moderators[0].user_id
1019 get amSiteCreator(): boolean {
1021 this.props.admins &&
1022 UserService.Instance.user &&
1023 this.props.post.creator_id != UserService.Instance.user.id &&
1024 UserService.Instance.user.id == this.props.admins[0].id
1028 handlePostLike(i: PostListing) {
1029 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1031 if (i.state.my_vote == 1) {
1034 } else if (i.state.my_vote == -1) {
1035 i.state.downvotes--;
1043 i.state.my_vote = new_vote;
1045 let form: CreatePostLikeForm = {
1046 post_id: i.props.post.id,
1047 score: i.state.my_vote,
1050 WebSocketService.Instance.likePost(form);
1051 i.setState(i.state);
1055 handlePostDisLike(i: PostListing) {
1056 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1058 if (i.state.my_vote == 1) {
1061 i.state.downvotes++;
1062 } else if (i.state.my_vote == -1) {
1063 i.state.downvotes--;
1066 i.state.downvotes++;
1070 i.state.my_vote = new_vote;
1072 let form: CreatePostLikeForm = {
1073 post_id: i.props.post.id,
1074 score: i.state.my_vote,
1077 WebSocketService.Instance.likePost(form);
1078 i.setState(i.state);
1082 handleEditClick(i: PostListing) {
1083 i.state.showEdit = true;
1084 i.setState(i.state);
1087 handleEditCancel() {
1088 this.state.showEdit = false;
1089 this.setState(this.state);
1092 // The actual editing is done in the recieve for post
1094 this.state.showEdit = false;
1095 this.setState(this.state);
1098 handleDeleteClick(i: PostListing) {
1099 let deleteForm: PostFormI = {
1100 body: i.props.post.body,
1101 community_id: i.props.post.community_id,
1102 name: i.props.post.name,
1103 url: i.props.post.url,
1104 edit_id: i.props.post.id,
1105 creator_id: i.props.post.creator_id,
1106 deleted: !i.props.post.deleted,
1107 nsfw: i.props.post.nsfw,
1110 WebSocketService.Instance.editPost(deleteForm);
1113 handleSavePostClick(i: PostListing) {
1114 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1115 let form: SavePostForm = {
1116 post_id: i.props.post.id,
1120 WebSocketService.Instance.savePost(form);
1123 get crossPostParams(): string {
1124 let params = `?title=${this.props.post.name}`;
1125 let post = this.props.post;
1128 params += `&url=${post.url}`;
1130 if (this.props.post.body) {
1131 params += `&body=${this.props.post.body}`;
1136 handleModRemoveShow(i: PostListing) {
1137 i.state.showRemoveDialog = true;
1138 i.setState(i.state);
1141 handleModRemoveReasonChange(i: PostListing, event: any) {
1142 i.state.removeReason = event.target.value;
1143 i.setState(i.state);
1146 handleModRemoveSubmit(i: PostListing) {
1147 event.preventDefault();
1148 let form: PostFormI = {
1149 name: i.props.post.name,
1150 community_id: i.props.post.community_id,
1151 edit_id: i.props.post.id,
1152 creator_id: i.props.post.creator_id,
1153 removed: !i.props.post.removed,
1154 reason: i.state.removeReason,
1155 nsfw: i.props.post.nsfw,
1158 WebSocketService.Instance.editPost(form);
1160 i.state.showRemoveDialog = false;
1161 i.setState(i.state);
1164 handleModLock(i: PostListing) {
1165 let form: PostFormI = {
1166 name: i.props.post.name,
1167 community_id: i.props.post.community_id,
1168 edit_id: i.props.post.id,
1169 creator_id: i.props.post.creator_id,
1170 nsfw: i.props.post.nsfw,
1171 locked: !i.props.post.locked,
1174 WebSocketService.Instance.editPost(form);
1177 handleModSticky(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 stickied: !i.props.post.stickied,
1187 WebSocketService.Instance.editPost(form);
1190 handleModBanFromCommunityShow(i: PostListing) {
1191 i.state.showBanDialog = true;
1192 i.state.banType = BanType.Community;
1193 i.setState(i.state);
1196 handleModBanShow(i: PostListing) {
1197 i.state.showBanDialog = true;
1198 i.state.banType = BanType.Site;
1199 i.setState(i.state);
1202 handleModBanReasonChange(i: PostListing, event: any) {
1203 i.state.banReason = event.target.value;
1204 i.setState(i.state);
1207 handleModBanExpiresChange(i: PostListing, event: any) {
1208 i.state.banExpires = event.target.value;
1209 i.setState(i.state);
1212 handleModBanFromCommunitySubmit(i: PostListing) {
1213 i.state.banType = BanType.Community;
1214 i.setState(i.state);
1215 i.handleModBanBothSubmit(i);
1218 handleModBanSubmit(i: PostListing) {
1219 i.state.banType = BanType.Site;
1220 i.setState(i.state);
1221 i.handleModBanBothSubmit(i);
1224 handleModBanBothSubmit(i: PostListing) {
1225 event.preventDefault();
1227 if (i.state.banType == BanType.Community) {
1228 let form: BanFromCommunityForm = {
1229 user_id: i.props.post.creator_id,
1230 community_id: i.props.post.community_id,
1231 ban: !i.props.post.banned_from_community,
1232 reason: i.state.banReason,
1233 expires: getUnixTime(i.state.banExpires),
1235 WebSocketService.Instance.banFromCommunity(form);
1237 let form: BanUserForm = {
1238 user_id: i.props.post.creator_id,
1239 ban: !i.props.post.banned,
1240 reason: i.state.banReason,
1241 expires: getUnixTime(i.state.banExpires),
1243 WebSocketService.Instance.banUser(form);
1246 i.state.showBanDialog = false;
1247 i.setState(i.state);
1250 handleAddModToCommunity(i: PostListing) {
1251 let form: AddModToCommunityForm = {
1252 user_id: i.props.post.creator_id,
1253 community_id: i.props.post.community_id,
1256 WebSocketService.Instance.addModToCommunity(form);
1257 i.setState(i.state);
1260 handleAddAdmin(i: PostListing) {
1261 let form: AddAdminForm = {
1262 user_id: i.props.post.creator_id,
1265 WebSocketService.Instance.addAdmin(form);
1266 i.setState(i.state);
1269 handleShowConfirmTransferCommunity(i: PostListing) {
1270 i.state.showConfirmTransferCommunity = true;
1271 i.setState(i.state);
1274 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1275 i.state.showConfirmTransferCommunity = false;
1276 i.setState(i.state);
1279 handleTransferCommunity(i: PostListing) {
1280 let form: TransferCommunityForm = {
1281 community_id: i.props.post.community_id,
1282 user_id: i.props.post.creator_id,
1284 WebSocketService.Instance.transferCommunity(form);
1285 i.state.showConfirmTransferCommunity = false;
1286 i.setState(i.state);
1289 handleShowConfirmTransferSite(i: PostListing) {
1290 i.state.showConfirmTransferSite = true;
1291 i.setState(i.state);
1294 handleCancelShowConfirmTransferSite(i: PostListing) {
1295 i.state.showConfirmTransferSite = false;
1296 i.setState(i.state);
1299 handleTransferSite(i: PostListing) {
1300 let form: TransferSiteForm = {
1301 user_id: i.props.post.creator_id,
1303 WebSocketService.Instance.transferSite(form);
1304 i.state.showConfirmTransferSite = false;
1305 i.setState(i.state);
1308 handleImageExpandClick(i: PostListing) {
1309 i.state.imageExpanded = !i.state.imageExpanded;
1310 i.setState(i.state);
1313 handleViewSource(i: PostListing) {
1314 i.state.viewSource = !i.state.viewSource;
1315 i.setState(i.state);
1318 handleShowAdvanced(i: PostListing) {
1319 i.state.showAdvanced = !i.state.showAdvanced;
1320 i.setState(i.state);
1324 get pointsTippy(): string {
1325 let points = i18n.t('number_of_points', {
1326 count: this.state.score,
1329 let upvotes = i18n.t('number_of_upvotes', {
1330 count: this.state.upvotes,
1333 let downvotes = i18n.t('number_of_downvotes', {
1334 count: this.state.downvotes,
1337 return `${points} • ${upvotes} • ${downvotes}`;