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';
29 pictshareAvatarThumbnail,
34 import { i18n } from '../i18next';
36 interface PostListingState {
38 showRemoveDialog: boolean;
40 showBanDialog: boolean;
44 showConfirmTransferSite: boolean;
45 showConfirmTransferCommunity: boolean;
46 imageExpanded: boolean;
48 showAdvanced: boolean;
55 interface PostListingProps {
57 showCommunity?: boolean;
59 moderators?: Array<CommunityUser>;
60 admins?: Array<UserView>;
63 export class PostListing extends Component<PostListingProps, PostListingState> {
64 private emptyState: PostListingState = {
66 showRemoveDialog: false,
71 banType: BanType.Community,
72 showConfirmTransferSite: false,
73 showConfirmTransferCommunity: false,
77 my_vote: this.props.post.my_vote,
78 score: this.props.post.score,
79 upvotes: this.props.post.upvotes,
80 downvotes: this.props.post.downvotes,
83 constructor(props: any, context: any) {
84 super(props, context);
86 this.state = this.emptyState;
87 this.handlePostLike = this.handlePostLike.bind(this);
88 this.handlePostDisLike = this.handlePostDisLike.bind(this);
89 this.handleEditPost = this.handleEditPost.bind(this);
90 this.handleEditCancel = this.handleEditCancel.bind(this);
93 componentWillReceiveProps(nextProps: PostListingProps) {
94 this.state.my_vote = nextProps.post.my_vote;
95 this.state.upvotes = nextProps.post.upvotes;
96 this.state.downvotes = nextProps.post.downvotes;
97 this.state.score = nextProps.post.score;
98 this.setState(this.state);
104 {!this.state.showEdit ? (
112 post={this.props.post}
113 onEdit={this.handleEditPost}
114 onCancel={this.handleEditCancel}
126 {this.props.post.url &&
127 this.props.showBody &&
128 this.props.post.embed_title && (
129 <IFramelyCard post={this.props.post} />
131 {this.props.showBody && this.props.post.body && (
133 {this.state.viewSource ? (
134 <pre>{this.props.post.body}</pre>
138 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
148 imgThumb(src: string) {
149 let post = this.props.post;
152 className={`img-fluid thumbnail rounded ${(post.nsfw ||
153 post.community_nsfw) &&
160 getImage(thumbnail: boolean = false) {
161 let post = this.props.post;
162 if (isImage(post.url)) {
163 if (post.url.includes('pictshare')) {
164 return pictshareImage(post.url, thumbnail);
168 } else if (post.thumbnail_url) {
169 return pictshareImage(post.thumbnail_url, thumbnail);
174 let post = this.props.post;
176 if (isImage(post.url)) {
179 class="text-body pointer"
180 data-tippy-content={i18n.t('expand_here')}
181 onClick={linkEvent(this, this.handleImageExpandClick)}
183 {this.imgThumb(this.getImage(true))}
184 <svg class="icon mini-overlay">
185 <use xlinkHref="#icon-image"></use>
189 } else if (post.thumbnail_url) {
192 className="text-body"
197 {this.imgThumb(this.getImage(true))}
198 <svg class="icon mini-overlay">
199 <use xlinkHref="#icon-external-link"></use>
203 } else if (post.url) {
204 if (isVideo(post.url)) {
206 <div class="embed-responsive embed-responsive-16by9">
212 class="embed-responsive-item"
214 <source src={post.url} type="video/mp4" />
221 className="text-body"
226 <svg class="icon thumbnail">
227 <use xlinkHref="#icon-external-link"></use>
235 className="text-body"
236 to={`/post/${post.id}`}
237 title={i18n.t('comments')}
239 <svg class="icon thumbnail">
240 <use xlinkHref="#icon-message-square"></use>
248 let post = this.props.post;
251 <div className={`vote-bar col-1 pr-0 small text-center`}>
253 className={`btn-animate btn btn-link p-0 ${
254 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
256 onClick={linkEvent(this, this.handlePostLike)}
257 data-tippy-content={i18n.t('upvote')}
259 <svg class="icon upvote">
260 <use xlinkHref="#icon-arrow-up"></use>
264 class={`unselectable pointer font-weight-bold text-muted px-1`}
265 data-tippy-content={this.pointsTippy}
269 {WebSocketService.Instance.site.enable_downvotes && (
271 className={`btn-animate btn btn-link p-0 ${
272 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
274 onClick={linkEvent(this, this.handlePostDisLike)}
275 data-tippy-content={i18n.t('downvote')}
277 <svg class="icon downvote">
278 <use xlinkHref="#icon-arrow-down"></use>
283 {!this.state.imageExpanded && (
284 <div class="col-3 col-sm-2 pr-0 mt-1">
285 <div class="position-relative">{this.thumbnail()}</div>
289 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
292 <div className="col-12">
293 <div className="post-title">
294 <h5 className="mb-0 d-inline">
295 {this.props.showBody && post.url ? (
297 className="text-body"
306 className="text-body"
307 to={`/post/${post.id}`}
308 title={i18n.t('comments')}
315 !(new URL(post.url).hostname == window.location.hostname) && (
316 <small class="d-inline-block">
318 className="ml-2 text-muted font-italic"
323 {new URL(post.url).hostname}
324 <svg class="ml-1 icon icon-inline">
325 <use xlinkHref="#icon-external-link"></use>
330 {(isImage(post.url) || this.props.post.thumbnail_url) && (
332 {!this.state.imageExpanded ? (
334 class="text-monospace unselectable pointer ml-2 text-muted small"
335 data-tippy-content={i18n.t('expand_here')}
336 onClick={linkEvent(this, this.handleImageExpandClick)}
338 <svg class="icon icon-inline">
339 <use xlinkHref="#icon-plus-square"></use>
345 class="text-monospace unselectable pointer ml-2 text-muted small"
346 onClick={linkEvent(this, this.handleImageExpandClick)}
348 <svg class="icon icon-inline">
349 <use xlinkHref="#icon-minus-square"></use>
357 this.handleImageExpandClick
361 class="img-fluid img-expanded"
362 src={this.getImage()}
371 <small className="ml-2 text-muted font-italic">
377 className="unselectable pointer ml-2 text-muted font-italic"
378 data-tippy-content={i18n.t('deleted')}
380 <svg class={`icon icon-inline text-danger`}>
381 <use xlinkHref="#icon-trash"></use>
387 className="unselectable pointer ml-2 text-muted font-italic"
388 data-tippy-content={i18n.t('locked')}
390 <svg class={`icon icon-inline text-danger`}>
391 <use xlinkHref="#icon-lock"></use>
397 className="unselectable pointer ml-2 text-muted font-italic"
398 data-tippy-content={i18n.t('stickied')}
400 <svg class={`icon icon-inline text-success`}>
401 <use xlinkHref="#icon-pin"></use>
406 <small className="ml-2 text-muted font-italic">
414 <div className="details col-12">
415 <ul class="list-inline mb-0 text-muted small">
416 <li className="list-inline-item">
417 <span>{i18n.t('by')} </span>
419 className="text-body font-weight-bold"
420 to={`/u/${post.creator_name}`}
422 {post.creator_avatar && showAvatars() && (
426 src={pictshareAvatarThumbnail(post.creator_avatar)}
427 class="rounded-circle mr-1"
430 <span>{post.creator_name}</span>
433 <span className="mx-1 badge badge-light">
438 <span className="mx-1 badge badge-light">
442 {(post.banned_from_community || post.banned) && (
443 <span className="mx-1 badge badge-danger">
447 {this.props.showCommunity && (
449 <span> {i18n.t('to')} </span>
450 <Link to={`/c/${post.community_name}`}>
451 {post.community_name}
456 <li className="list-inline-item">•</li>
457 <li className="list-inline-item">
459 <MomentTime data={post} />
462 <li className="list-inline-item">•</li>
463 {this.state.upvotes !== this.state.score && (
466 class="unselectable pointer mr-2"
467 data-tippy-content={this.pointsTippy}
469 <li className="list-inline-item">
470 <span className="text-muted">
471 <svg class="small icon icon-inline mr-1">
472 <use xlinkHref="#icon-arrow-up"></use>
477 <li className="list-inline-item">
478 <span className="text-muted">
479 <svg class="small icon icon-inline mr-1">
480 <use xlinkHref="#icon-arrow-down"></use>
482 {this.state.downvotes}
486 <li className="list-inline-item">•</li>
489 <li className="list-inline-item">
491 className="text-muted"
492 title={i18n.t('number_of_comments', {
493 count: post.number_of_comments,
495 to={`/post/${post.id}`}
497 <svg class="mr-1 icon icon-inline">
498 <use xlinkHref="#icon-message-square"></use>
500 {post.number_of_comments}
504 {this.props.post.duplicates && (
505 <ul class="list-inline mb-1 small text-muted">
507 <li className="list-inline-item mr-2">
508 {i18n.t('cross_posted_to')}
510 {this.props.post.duplicates.map(post => (
511 <li className="list-inline-item mr-2">
512 <Link to={`/post/${post.id}`}>
513 {post.community_name}
520 <ul class="list-inline mb-1 text-muted font-weight-bold">
521 {UserService.Instance.user && (
523 {this.props.showBody && (
525 <li className="list-inline-item">
527 class="btn btn-link btn-animate text-muted"
528 onClick={linkEvent(this, this.handleSavePostClick)}
530 post.saved ? i18n.t('unsave') : i18n.t('save')
534 class={`icon icon-inline ${post.saved &&
537 <use xlinkHref="#icon-star"></use>
541 <li className="list-inline-item">
543 class="btn btn-link btn-animate text-muted"
544 to={`/create_post${this.crossPostParams}`}
545 title={i18n.t('cross_post')}
547 <svg class="icon icon-inline">
548 <use xlinkHref="#icon-copy"></use>
554 {this.myPost && this.props.showBody && (
556 <li className="list-inline-item">
558 class="btn btn-link btn-animate text-muted"
559 onClick={linkEvent(this, this.handleEditClick)}
560 data-tippy-content={i18n.t('edit')}
562 <svg class="icon icon-inline">
563 <use xlinkHref="#icon-edit"></use>
567 <li className="list-inline-item">
569 class="btn btn-link btn-animate text-muted"
570 onClick={linkEvent(this, this.handleDeleteClick)}
578 class={`icon icon-inline ${post.deleted &&
581 <use xlinkHref="#icon-trash"></use>
588 {!this.state.showAdvanced && this.props.showBody ? (
589 <li className="list-inline-item">
591 class="btn btn-link btn-animate text-muted"
592 onClick={linkEvent(this, this.handleShowAdvanced)}
593 data-tippy-content={i18n.t('more')}
595 <svg class="icon icon-inline">
596 <use xlinkHref="#icon-more-vertical"></use>
602 {this.props.showBody && post.body && (
603 <li className="list-inline-item">
605 class="btn btn-link btn-animate text-muted"
606 onClick={linkEvent(this, this.handleViewSource)}
607 data-tippy-content={i18n.t('view_source')}
610 class={`icon icon-inline ${this.state
611 .viewSource && 'text-success'}`}
613 <use xlinkHref="#icon-file-text"></use>
618 {this.canModOnSelf && (
620 <li className="list-inline-item">
622 class="btn btn-link btn-animate text-muted"
623 onClick={linkEvent(this, this.handleModLock)}
631 class={`icon icon-inline ${post.locked &&
634 <use xlinkHref="#icon-lock"></use>
638 <li className="list-inline-item">
640 class="btn btn-link btn-animate text-muted"
641 onClick={linkEvent(this, this.handleModSticky)}
649 class={`icon icon-inline ${post.stickied &&
652 <use xlinkHref="#icon-pin"></use>
658 {/* Mods can ban from community, and appoint as mods to community */}
659 {(this.canMod || this.canAdmin) && (
660 <li className="list-inline-item">
666 this.handleModRemoveShow
676 this.handleModRemoveSubmit
687 <li className="list-inline-item">
688 {!post.banned_from_community ? (
693 this.handleModBanFromCommunityShow
703 this.handleModBanFromCommunitySubmit
711 {!post.banned_from_community && (
712 <li className="list-inline-item">
717 this.handleAddModToCommunity
721 ? i18n.t('remove_as_mod')
722 : i18n.t('appoint_as_mod')}
728 {/* Community creators and admins can transfer community to another mod */}
729 {(this.amCommunityCreator || this.canAdmin) &&
731 <li className="list-inline-item">
732 {!this.state.showConfirmTransferCommunity ? (
737 this.handleShowConfirmTransferCommunity
740 {i18n.t('transfer_community')}
744 <span class="d-inline-block mr-1">
745 {i18n.t('are_you_sure')}
748 class="pointer d-inline-block mr-1"
751 this.handleTransferCommunity
757 class="pointer d-inline-block"
761 .handleCancelShowConfirmTransferCommunity
770 {/* Admins can ban from all, and appoint other admins */}
774 <li className="list-inline-item">
780 this.handleModBanShow
783 {i18n.t('ban_from_site')}
790 this.handleModBanSubmit
793 {i18n.t('unban_from_site')}
799 <li className="list-inline-item">
802 onClick={linkEvent(this, this.handleAddAdmin)}
805 ? i18n.t('remove_as_admin')
806 : i18n.t('appoint_as_admin')}
812 {/* Site Creator can transfer to another admin */}
813 {this.amSiteCreator && this.isAdmin && (
814 <li className="list-inline-item">
815 {!this.state.showConfirmTransferSite ? (
820 this.handleShowConfirmTransferSite
823 {i18n.t('transfer_site')}
827 <span class="d-inline-block mr-1">
828 {i18n.t('are_you_sure')}
831 class="pointer d-inline-block mr-1"
834 this.handleTransferSite
840 class="pointer d-inline-block"
843 this.handleCancelShowConfirmTransferSite
857 {this.state.showRemoveDialog && (
860 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
864 class="form-control mr-2"
865 placeholder={i18n.t('reason')}
866 value={this.state.removeReason}
867 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
869 <button type="submit" class="btn btn-secondary">
870 {i18n.t('remove_post')}
874 {this.state.showBanDialog && (
875 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
876 <div class="form-group row">
877 <label class="col-form-label" htmlFor="post-listing-reason">
882 id="post-listing-reason"
883 class="form-control mr-2"
884 placeholder={i18n.t('reason')}
885 value={this.state.banReason}
886 onInput={linkEvent(this, this.handleModBanReasonChange)}
889 {/* TODO hold off on expires until later */}
890 {/* <div class="form-group row"> */}
891 {/* <label class="col-form-label">Expires</label> */}
892 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
894 <div class="form-group row">
895 <button type="submit" class="btn btn-secondary">
896 {i18n.t('ban')} {post.creator_name}
908 private get myPost(): boolean {
910 UserService.Instance.user &&
911 this.props.post.creator_id == UserService.Instance.user.id
915 get isMod(): boolean {
917 this.props.moderators &&
919 this.props.moderators.map(m => m.user_id),
920 this.props.post.creator_id
925 get isAdmin(): boolean {
929 this.props.admins.map(a => a.id),
930 this.props.post.creator_id
935 get canMod(): boolean {
936 if (this.props.admins && this.props.moderators) {
937 let adminsThenMods = this.props.admins
939 .concat(this.props.moderators.map(m => m.user_id));
942 UserService.Instance.user,
944 this.props.post.creator_id
951 get canModOnSelf(): boolean {
952 if (this.props.admins && this.props.moderators) {
953 let adminsThenMods = this.props.admins
955 .concat(this.props.moderators.map(m => m.user_id));
958 UserService.Instance.user,
960 this.props.post.creator_id,
968 get canAdmin(): boolean {
972 UserService.Instance.user,
973 this.props.admins.map(a => a.id),
974 this.props.post.creator_id
979 get amCommunityCreator(): boolean {
981 this.props.moderators &&
982 UserService.Instance.user &&
983 this.props.post.creator_id != UserService.Instance.user.id &&
984 UserService.Instance.user.id == this.props.moderators[0].user_id
988 get amSiteCreator(): boolean {
991 UserService.Instance.user &&
992 this.props.post.creator_id != UserService.Instance.user.id &&
993 UserService.Instance.user.id == this.props.admins[0].id
997 handlePostLike(i: PostListing) {
998 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1000 if (i.state.my_vote == 1) {
1003 } else if (i.state.my_vote == -1) {
1004 i.state.downvotes--;
1012 i.state.my_vote = new_vote;
1014 let form: CreatePostLikeForm = {
1015 post_id: i.props.post.id,
1016 score: i.state.my_vote,
1019 WebSocketService.Instance.likePost(form);
1020 i.setState(i.state);
1024 handlePostDisLike(i: PostListing) {
1025 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1027 if (i.state.my_vote == 1) {
1030 i.state.downvotes++;
1031 } else if (i.state.my_vote == -1) {
1032 i.state.downvotes--;
1035 i.state.downvotes++;
1039 i.state.my_vote = new_vote;
1041 let form: CreatePostLikeForm = {
1042 post_id: i.props.post.id,
1043 score: i.state.my_vote,
1046 WebSocketService.Instance.likePost(form);
1047 i.setState(i.state);
1051 handleEditClick(i: PostListing) {
1052 i.state.showEdit = true;
1053 i.setState(i.state);
1056 handleEditCancel() {
1057 this.state.showEdit = false;
1058 this.setState(this.state);
1061 // The actual editing is done in the recieve for post
1063 this.state.showEdit = false;
1064 this.setState(this.state);
1067 handleDeleteClick(i: PostListing) {
1068 let deleteForm: PostFormI = {
1069 body: i.props.post.body,
1070 community_id: i.props.post.community_id,
1071 name: i.props.post.name,
1072 url: i.props.post.url,
1073 edit_id: i.props.post.id,
1074 creator_id: i.props.post.creator_id,
1075 deleted: !i.props.post.deleted,
1076 nsfw: i.props.post.nsfw,
1079 WebSocketService.Instance.editPost(deleteForm);
1082 handleSavePostClick(i: PostListing) {
1083 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1084 let form: SavePostForm = {
1085 post_id: i.props.post.id,
1089 WebSocketService.Instance.savePost(form);
1092 get crossPostParams(): string {
1093 let params = `?title=${this.props.post.name}`;
1094 let post = this.props.post;
1097 params += `&url=${post.url}`;
1099 if (this.props.post.body) {
1100 params += `&body=${this.props.post.body}`;
1105 handleModRemoveShow(i: PostListing) {
1106 i.state.showRemoveDialog = true;
1107 i.setState(i.state);
1110 handleModRemoveReasonChange(i: PostListing, event: any) {
1111 i.state.removeReason = event.target.value;
1112 i.setState(i.state);
1115 handleModRemoveSubmit(i: PostListing) {
1116 event.preventDefault();
1117 let form: PostFormI = {
1118 name: i.props.post.name,
1119 community_id: i.props.post.community_id,
1120 edit_id: i.props.post.id,
1121 creator_id: i.props.post.creator_id,
1122 removed: !i.props.post.removed,
1123 reason: i.state.removeReason,
1124 nsfw: i.props.post.nsfw,
1127 WebSocketService.Instance.editPost(form);
1129 i.state.showRemoveDialog = false;
1130 i.setState(i.state);
1133 handleModLock(i: PostListing) {
1134 let form: PostFormI = {
1135 name: i.props.post.name,
1136 community_id: i.props.post.community_id,
1137 edit_id: i.props.post.id,
1138 creator_id: i.props.post.creator_id,
1139 nsfw: i.props.post.nsfw,
1140 locked: !i.props.post.locked,
1143 WebSocketService.Instance.editPost(form);
1146 handleModSticky(i: PostListing) {
1147 let form: PostFormI = {
1148 name: i.props.post.name,
1149 community_id: i.props.post.community_id,
1150 edit_id: i.props.post.id,
1151 creator_id: i.props.post.creator_id,
1152 nsfw: i.props.post.nsfw,
1153 stickied: !i.props.post.stickied,
1156 WebSocketService.Instance.editPost(form);
1159 handleModBanFromCommunityShow(i: PostListing) {
1160 i.state.showBanDialog = true;
1161 i.state.banType = BanType.Community;
1162 i.setState(i.state);
1165 handleModBanShow(i: PostListing) {
1166 i.state.showBanDialog = true;
1167 i.state.banType = BanType.Site;
1168 i.setState(i.state);
1171 handleModBanReasonChange(i: PostListing, event: any) {
1172 i.state.banReason = event.target.value;
1173 i.setState(i.state);
1176 handleModBanExpiresChange(i: PostListing, event: any) {
1177 i.state.banExpires = event.target.value;
1178 i.setState(i.state);
1181 handleModBanFromCommunitySubmit(i: PostListing) {
1182 i.state.banType = BanType.Community;
1183 i.setState(i.state);
1184 i.handleModBanBothSubmit(i);
1187 handleModBanSubmit(i: PostListing) {
1188 i.state.banType = BanType.Site;
1189 i.setState(i.state);
1190 i.handleModBanBothSubmit(i);
1193 handleModBanBothSubmit(i: PostListing) {
1194 event.preventDefault();
1196 if (i.state.banType == BanType.Community) {
1197 let form: BanFromCommunityForm = {
1198 user_id: i.props.post.creator_id,
1199 community_id: i.props.post.community_id,
1200 ban: !i.props.post.banned_from_community,
1201 reason: i.state.banReason,
1202 expires: getUnixTime(i.state.banExpires),
1204 WebSocketService.Instance.banFromCommunity(form);
1206 let form: BanUserForm = {
1207 user_id: i.props.post.creator_id,
1208 ban: !i.props.post.banned,
1209 reason: i.state.banReason,
1210 expires: getUnixTime(i.state.banExpires),
1212 WebSocketService.Instance.banUser(form);
1215 i.state.showBanDialog = false;
1216 i.setState(i.state);
1219 handleAddModToCommunity(i: PostListing) {
1220 let form: AddModToCommunityForm = {
1221 user_id: i.props.post.creator_id,
1222 community_id: i.props.post.community_id,
1225 WebSocketService.Instance.addModToCommunity(form);
1226 i.setState(i.state);
1229 handleAddAdmin(i: PostListing) {
1230 let form: AddAdminForm = {
1231 user_id: i.props.post.creator_id,
1234 WebSocketService.Instance.addAdmin(form);
1235 i.setState(i.state);
1238 handleShowConfirmTransferCommunity(i: PostListing) {
1239 i.state.showConfirmTransferCommunity = true;
1240 i.setState(i.state);
1243 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1244 i.state.showConfirmTransferCommunity = false;
1245 i.setState(i.state);
1248 handleTransferCommunity(i: PostListing) {
1249 let form: TransferCommunityForm = {
1250 community_id: i.props.post.community_id,
1251 user_id: i.props.post.creator_id,
1253 WebSocketService.Instance.transferCommunity(form);
1254 i.state.showConfirmTransferCommunity = false;
1255 i.setState(i.state);
1258 handleShowConfirmTransferSite(i: PostListing) {
1259 i.state.showConfirmTransferSite = true;
1260 i.setState(i.state);
1263 handleCancelShowConfirmTransferSite(i: PostListing) {
1264 i.state.showConfirmTransferSite = false;
1265 i.setState(i.state);
1268 handleTransferSite(i: PostListing) {
1269 let form: TransferSiteForm = {
1270 user_id: i.props.post.creator_id,
1272 WebSocketService.Instance.transferSite(form);
1273 i.state.showConfirmTransferSite = false;
1274 i.setState(i.state);
1277 handleImageExpandClick(i: PostListing) {
1278 i.state.imageExpanded = !i.state.imageExpanded;
1279 i.setState(i.state);
1282 handleViewSource(i: PostListing) {
1283 i.state.viewSource = !i.state.viewSource;
1284 i.setState(i.state);
1287 handleShowAdvanced(i: PostListing) {
1288 i.state.showAdvanced = !i.state.showAdvanced;
1289 i.setState(i.state);
1293 get pointsTippy(): string {
1294 let points = i18n.t('number_of_points', {
1295 count: this.state.score,
1298 let upvotes = i18n.t('number_of_upvotes', {
1299 count: this.state.upvotes,
1302 let downvotes = i18n.t('number_of_downvotes', {
1303 count: this.state.downvotes,
1306 return `${points} • ${upvotes} • ${downvotes}`;