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,
35 import { i18n } from '../i18next';
37 interface PostListingState {
39 showRemoveDialog: boolean;
41 showBanDialog: boolean;
45 showConfirmTransferSite: boolean;
46 showConfirmTransferCommunity: boolean;
47 imageExpanded: boolean;
49 showAdvanced: boolean;
56 interface PostListingProps {
58 showCommunity?: boolean;
60 moderators?: Array<CommunityUser>;
61 admins?: Array<UserView>;
64 export class PostListing extends Component<PostListingProps, PostListingState> {
65 private emptyState: PostListingState = {
67 showRemoveDialog: false,
72 banType: BanType.Community,
73 showConfirmTransferSite: false,
74 showConfirmTransferCommunity: false,
78 my_vote: this.props.post.my_vote,
79 score: this.props.post.score,
80 upvotes: this.props.post.upvotes,
81 downvotes: this.props.post.downvotes,
84 constructor(props: any, context: any) {
85 super(props, context);
87 this.state = this.emptyState;
88 this.handlePostLike = this.handlePostLike.bind(this);
89 this.handlePostDisLike = this.handlePostDisLike.bind(this);
90 this.handleEditPost = this.handleEditPost.bind(this);
91 this.handleEditCancel = this.handleEditCancel.bind(this);
94 componentWillReceiveProps(nextProps: PostListingProps) {
95 this.state.my_vote = nextProps.post.my_vote;
96 this.state.upvotes = nextProps.post.upvotes;
97 this.state.downvotes = nextProps.post.downvotes;
98 this.state.score = nextProps.post.score;
99 this.setState(this.state);
105 {!this.state.showEdit ? (
113 post={this.props.post}
114 onEdit={this.handleEditPost}
115 onCancel={this.handleEditCancel}
127 {this.props.post.url &&
128 this.props.showBody &&
129 this.props.post.embed_title && (
130 <IFramelyCard post={this.props.post} />
132 {this.props.showBody && this.props.post.body && (
134 {this.state.viewSource ? (
135 <pre>{this.props.post.body}</pre>
139 dangerouslySetInnerHTML={mdToHtml(this.props.post.body)}
149 imgThumb(src: string) {
150 let post = this.props.post;
153 className={`img-fluid thumbnail rounded ${
154 (post.nsfw || post.community_nsfw) && 'img-blur'
161 getImage(thumbnail: boolean = false) {
162 let post = this.props.post;
163 if (isImage(post.url)) {
164 if (post.url.includes('pictshare')) {
165 return pictshareImage(post.url, thumbnail);
169 } else if (post.thumbnail_url) {
170 return pictshareImage(post.thumbnail_url, thumbnail);
175 let post = this.props.post;
177 if (isImage(post.url)) {
180 class="text-body pointer"
181 data-tippy-content={i18n.t('expand_here')}
182 onClick={linkEvent(this, this.handleImageExpandClick)}
184 {this.imgThumb(this.getImage(true))}
185 <svg class="icon mini-overlay">
186 <use xlinkHref="#icon-image"></use>
190 } else if (post.thumbnail_url) {
193 className="text-body"
198 {this.imgThumb(this.getImage(true))}
199 <svg class="icon mini-overlay">
200 <use xlinkHref="#icon-external-link"></use>
204 } else if (post.url) {
205 if (isVideo(post.url)) {
207 <div class="embed-responsive embed-responsive-16by9">
213 class="embed-responsive-item"
215 <source src={post.url} type="video/mp4" />
222 className="text-body"
227 <svg class="icon thumbnail">
228 <use xlinkHref="#icon-external-link"></use>
236 className="text-body"
237 to={`/post/${post.id}`}
238 title={i18n.t('comments')}
240 <svg class="icon thumbnail">
241 <use xlinkHref="#icon-message-square"></use>
249 let post = this.props.post;
252 <div className={`vote-bar col-1 pr-0 small text-center`}>
254 className={`btn-animate btn btn-link p-0 ${
255 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
257 onClick={linkEvent(this, this.handlePostLike)}
258 data-tippy-content={i18n.t('upvote')}
260 <svg class="icon upvote">
261 <use xlinkHref="#icon-arrow-up1"></use>
265 class={`unselectable pointer font-weight-bold text-muted px-1`}
266 data-tippy-content={this.pointsTippy}
270 {WebSocketService.Instance.site.enable_downvotes && (
272 className={`btn-animate btn btn-link p-0 ${
273 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
275 onClick={linkEvent(this, this.handlePostDisLike)}
276 data-tippy-content={i18n.t('downvote')}
278 <svg class="icon downvote">
279 <use xlinkHref="#icon-arrow-down1"></use>
284 {!this.state.imageExpanded && (
285 <div class="col-3 col-sm-2 pr-0 mt-1">
286 <div class="position-relative">{this.thumbnail()}</div>
290 class={`${this.state.imageExpanded ? 'col-12' : 'col-8 col-sm-9'}`}
293 <div className="col-12">
294 <div className="post-title">
295 <h5 className="mb-0 d-inline">
296 {this.props.showBody && post.url ? (
298 className="text-body"
307 className="text-body"
308 to={`/post/${post.id}`}
309 title={i18n.t('comments')}
315 {post.url && !(hostname(post.url) == window.location.hostname) && (
316 <small class="d-inline-block">
318 className="ml-2 text-muted font-italic"
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>
451 <Link to={`/c/${post.community_name}`}>
452 {post.community_name}
455 <a href={post.community_actor_id} target="_blank">
456 {hostname(post.ap_id)}/{post.community_name}
462 <li className="list-inline-item">•</li>
463 <li className="list-inline-item">
465 <MomentTime data={post} />
468 <li className="list-inline-item">•</li>
469 {this.state.upvotes !== this.state.score && (
472 class="unselectable pointer mr-2"
473 data-tippy-content={this.pointsTippy}
475 <li className="list-inline-item">
476 <span className="text-muted">
477 <svg class="small icon icon-inline mr-1">
478 <use xlinkHref="#icon-arrow-up"></use>
483 <li className="list-inline-item">
484 <span className="text-muted">
485 <svg class="small icon icon-inline mr-1">
486 <use xlinkHref="#icon-arrow-down"></use>
488 {this.state.downvotes}
492 <li className="list-inline-item">•</li>
495 <li className="list-inline-item">
497 className="text-muted"
498 title={i18n.t('number_of_comments', {
499 count: post.number_of_comments,
501 to={`/post/${post.id}`}
503 <svg class="mr-1 icon icon-inline">
504 <use xlinkHref="#icon-message-square"></use>
506 {post.number_of_comments}
510 {this.props.post.duplicates && (
511 <ul class="list-inline mb-1 small text-muted">
513 <li className="list-inline-item mr-2">
514 {i18n.t('cross_posted_to')}
516 {this.props.post.duplicates.map(post => (
517 <li className="list-inline-item mr-2">
518 <Link to={`/post/${post.id}`}>
519 {post.community_name}
526 <ul class="list-inline mb-1 text-muted font-weight-bold">
527 {UserService.Instance.user && (
529 {this.props.showBody && (
531 <li className="list-inline-item">
533 class="btn btn-sm btn-link btn-animate text-muted"
534 onClick={linkEvent(this, this.handleSavePostClick)}
536 post.saved ? i18n.t('unsave') : i18n.t('save')
540 class={`icon icon-inline ${
541 post.saved && 'text-warning'
544 <use xlinkHref="#icon-star"></use>
548 <li className="list-inline-item">
550 class="btn btn-sm btn-link btn-animate text-muted"
551 to={`/create_post${this.crossPostParams}`}
552 title={i18n.t('cross_post')}
554 <svg class="icon icon-inline">
555 <use xlinkHref="#icon-copy"></use>
561 {this.myPost && this.props.showBody && (
563 <li className="list-inline-item">
565 class="btn btn-sm btn-link btn-animate text-muted"
566 onClick={linkEvent(this, this.handleEditClick)}
567 data-tippy-content={i18n.t('edit')}
569 <svg class="icon icon-inline">
570 <use xlinkHref="#icon-edit"></use>
574 <li className="list-inline-item">
576 class="btn btn-sm btn-link btn-animate text-muted"
577 onClick={linkEvent(this, this.handleDeleteClick)}
585 class={`icon icon-inline ${
586 post.deleted && 'text-danger'
589 <use xlinkHref="#icon-trash"></use>
596 {!this.state.showAdvanced && this.props.showBody ? (
597 <li className="list-inline-item">
599 class="btn btn-sm btn-link btn-animate text-muted"
600 onClick={linkEvent(this, this.handleShowAdvanced)}
601 data-tippy-content={i18n.t('more')}
603 <svg class="icon icon-inline">
604 <use xlinkHref="#icon-more-vertical"></use>
610 {this.props.showBody && post.body && (
611 <li className="list-inline-item">
613 class="btn btn-sm btn-link btn-animate text-muted"
614 onClick={linkEvent(this, this.handleViewSource)}
615 data-tippy-content={i18n.t('view_source')}
618 class={`icon icon-inline ${
619 this.state.viewSource && 'text-success'
622 <use xlinkHref="#icon-file-text"></use>
627 {this.canModOnSelf && (
629 <li className="list-inline-item">
631 class="btn btn-sm btn-link btn-animate text-muted"
632 onClick={linkEvent(this, this.handleModLock)}
640 class={`icon icon-inline ${
641 post.locked && 'text-danger'
644 <use xlinkHref="#icon-lock"></use>
648 <li className="list-inline-item">
650 class="btn btn-sm btn-link btn-animate text-muted"
651 onClick={linkEvent(this, this.handleModSticky)}
659 class={`icon icon-inline ${
660 post.stickied && 'text-success'
663 <use xlinkHref="#icon-pin"></use>
669 {/* Mods can ban from community, and appoint as mods to community */}
670 {(this.canMod || this.canAdmin) && (
671 <li className="list-inline-item">
677 this.handleModRemoveShow
687 this.handleModRemoveSubmit
698 <li className="list-inline-item">
699 {!post.banned_from_community ? (
704 this.handleModBanFromCommunityShow
714 this.handleModBanFromCommunitySubmit
722 {!post.banned_from_community && (
723 <li className="list-inline-item">
728 this.handleAddModToCommunity
732 ? i18n.t('remove_as_mod')
733 : i18n.t('appoint_as_mod')}
739 {/* Community creators and admins can transfer community to another mod */}
740 {(this.amCommunityCreator || this.canAdmin) &&
742 <li className="list-inline-item">
743 {!this.state.showConfirmTransferCommunity ? (
748 this.handleShowConfirmTransferCommunity
751 {i18n.t('transfer_community')}
755 <span class="d-inline-block mr-1">
756 {i18n.t('are_you_sure')}
759 class="pointer d-inline-block mr-1"
762 this.handleTransferCommunity
768 class="pointer d-inline-block"
772 .handleCancelShowConfirmTransferCommunity
781 {/* Admins can ban from all, and appoint other admins */}
785 <li className="list-inline-item">
791 this.handleModBanShow
794 {i18n.t('ban_from_site')}
801 this.handleModBanSubmit
804 {i18n.t('unban_from_site')}
810 <li className="list-inline-item">
813 onClick={linkEvent(this, this.handleAddAdmin)}
816 ? i18n.t('remove_as_admin')
817 : i18n.t('appoint_as_admin')}
823 {/* Site Creator can transfer to another admin */}
824 {this.amSiteCreator && this.isAdmin && (
825 <li className="list-inline-item">
826 {!this.state.showConfirmTransferSite ? (
831 this.handleShowConfirmTransferSite
834 {i18n.t('transfer_site')}
838 <span class="d-inline-block mr-1">
839 {i18n.t('are_you_sure')}
842 class="pointer d-inline-block mr-1"
845 this.handleTransferSite
851 class="pointer d-inline-block"
854 this.handleCancelShowConfirmTransferSite
868 {this.state.showRemoveDialog && (
871 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
875 class="form-control mr-2"
876 placeholder={i18n.t('reason')}
877 value={this.state.removeReason}
878 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
880 <button type="submit" class="btn btn-secondary">
881 {i18n.t('remove_post')}
885 {this.state.showBanDialog && (
886 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
887 <div class="form-group row">
888 <label class="col-form-label" htmlFor="post-listing-reason">
893 id="post-listing-reason"
894 class="form-control mr-2"
895 placeholder={i18n.t('reason')}
896 value={this.state.banReason}
897 onInput={linkEvent(this, this.handleModBanReasonChange)}
900 {/* TODO hold off on expires until later */}
901 {/* <div class="form-group row"> */}
902 {/* <label class="col-form-label">Expires</label> */}
903 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
905 <div class="form-group row">
906 <button type="submit" class="btn btn-secondary">
907 {i18n.t('ban')} {post.creator_name}
919 private get myPost(): boolean {
921 UserService.Instance.user &&
922 this.props.post.creator_id == UserService.Instance.user.id
926 get isMod(): boolean {
928 this.props.moderators &&
930 this.props.moderators.map(m => m.user_id),
931 this.props.post.creator_id
936 get isAdmin(): boolean {
940 this.props.admins.map(a => a.id),
941 this.props.post.creator_id
946 get canMod(): boolean {
947 if (this.props.admins && this.props.moderators) {
948 let adminsThenMods = this.props.admins
950 .concat(this.props.moderators.map(m => m.user_id));
953 UserService.Instance.user,
955 this.props.post.creator_id
962 get canModOnSelf(): boolean {
963 if (this.props.admins && this.props.moderators) {
964 let adminsThenMods = this.props.admins
966 .concat(this.props.moderators.map(m => m.user_id));
969 UserService.Instance.user,
971 this.props.post.creator_id,
979 get canAdmin(): boolean {
983 UserService.Instance.user,
984 this.props.admins.map(a => a.id),
985 this.props.post.creator_id
990 get amCommunityCreator(): boolean {
992 this.props.moderators &&
993 UserService.Instance.user &&
994 this.props.post.creator_id != UserService.Instance.user.id &&
995 UserService.Instance.user.id == this.props.moderators[0].user_id
999 get amSiteCreator(): boolean {
1001 this.props.admins &&
1002 UserService.Instance.user &&
1003 this.props.post.creator_id != UserService.Instance.user.id &&
1004 UserService.Instance.user.id == this.props.admins[0].id
1008 handlePostLike(i: PostListing) {
1009 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1011 if (i.state.my_vote == 1) {
1014 } else if (i.state.my_vote == -1) {
1015 i.state.downvotes--;
1023 i.state.my_vote = new_vote;
1025 let form: CreatePostLikeForm = {
1026 post_id: i.props.post.id,
1027 score: i.state.my_vote,
1030 WebSocketService.Instance.likePost(form);
1031 i.setState(i.state);
1035 handlePostDisLike(i: PostListing) {
1036 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1038 if (i.state.my_vote == 1) {
1041 i.state.downvotes++;
1042 } else if (i.state.my_vote == -1) {
1043 i.state.downvotes--;
1046 i.state.downvotes++;
1050 i.state.my_vote = new_vote;
1052 let form: CreatePostLikeForm = {
1053 post_id: i.props.post.id,
1054 score: i.state.my_vote,
1057 WebSocketService.Instance.likePost(form);
1058 i.setState(i.state);
1062 handleEditClick(i: PostListing) {
1063 i.state.showEdit = true;
1064 i.setState(i.state);
1067 handleEditCancel() {
1068 this.state.showEdit = false;
1069 this.setState(this.state);
1072 // The actual editing is done in the recieve for post
1074 this.state.showEdit = false;
1075 this.setState(this.state);
1078 handleDeleteClick(i: PostListing) {
1079 let deleteForm: PostFormI = {
1080 body: i.props.post.body,
1081 community_id: i.props.post.community_id,
1082 name: i.props.post.name,
1083 url: i.props.post.url,
1084 edit_id: i.props.post.id,
1085 creator_id: i.props.post.creator_id,
1086 deleted: !i.props.post.deleted,
1087 nsfw: i.props.post.nsfw,
1090 WebSocketService.Instance.editPost(deleteForm);
1093 handleSavePostClick(i: PostListing) {
1094 let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
1095 let form: SavePostForm = {
1096 post_id: i.props.post.id,
1100 WebSocketService.Instance.savePost(form);
1103 get crossPostParams(): string {
1104 let params = `?title=${this.props.post.name}`;
1105 let post = this.props.post;
1108 params += `&url=${post.url}`;
1110 if (this.props.post.body) {
1111 params += `&body=${this.props.post.body}`;
1116 handleModRemoveShow(i: PostListing) {
1117 i.state.showRemoveDialog = true;
1118 i.setState(i.state);
1121 handleModRemoveReasonChange(i: PostListing, event: any) {
1122 i.state.removeReason = event.target.value;
1123 i.setState(i.state);
1126 handleModRemoveSubmit(i: PostListing) {
1127 event.preventDefault();
1128 let form: PostFormI = {
1129 name: i.props.post.name,
1130 community_id: i.props.post.community_id,
1131 edit_id: i.props.post.id,
1132 creator_id: i.props.post.creator_id,
1133 removed: !i.props.post.removed,
1134 reason: i.state.removeReason,
1135 nsfw: i.props.post.nsfw,
1138 WebSocketService.Instance.editPost(form);
1140 i.state.showRemoveDialog = false;
1141 i.setState(i.state);
1144 handleModLock(i: PostListing) {
1145 let form: PostFormI = {
1146 name: i.props.post.name,
1147 community_id: i.props.post.community_id,
1148 edit_id: i.props.post.id,
1149 creator_id: i.props.post.creator_id,
1150 nsfw: i.props.post.nsfw,
1151 locked: !i.props.post.locked,
1154 WebSocketService.Instance.editPost(form);
1157 handleModSticky(i: PostListing) {
1158 let form: PostFormI = {
1159 name: i.props.post.name,
1160 community_id: i.props.post.community_id,
1161 edit_id: i.props.post.id,
1162 creator_id: i.props.post.creator_id,
1163 nsfw: i.props.post.nsfw,
1164 stickied: !i.props.post.stickied,
1167 WebSocketService.Instance.editPost(form);
1170 handleModBanFromCommunityShow(i: PostListing) {
1171 i.state.showBanDialog = true;
1172 i.state.banType = BanType.Community;
1173 i.setState(i.state);
1176 handleModBanShow(i: PostListing) {
1177 i.state.showBanDialog = true;
1178 i.state.banType = BanType.Site;
1179 i.setState(i.state);
1182 handleModBanReasonChange(i: PostListing, event: any) {
1183 i.state.banReason = event.target.value;
1184 i.setState(i.state);
1187 handleModBanExpiresChange(i: PostListing, event: any) {
1188 i.state.banExpires = event.target.value;
1189 i.setState(i.state);
1192 handleModBanFromCommunitySubmit(i: PostListing) {
1193 i.state.banType = BanType.Community;
1194 i.setState(i.state);
1195 i.handleModBanBothSubmit(i);
1198 handleModBanSubmit(i: PostListing) {
1199 i.state.banType = BanType.Site;
1200 i.setState(i.state);
1201 i.handleModBanBothSubmit(i);
1204 handleModBanBothSubmit(i: PostListing) {
1205 event.preventDefault();
1207 if (i.state.banType == BanType.Community) {
1208 let form: BanFromCommunityForm = {
1209 user_id: i.props.post.creator_id,
1210 community_id: i.props.post.community_id,
1211 ban: !i.props.post.banned_from_community,
1212 reason: i.state.banReason,
1213 expires: getUnixTime(i.state.banExpires),
1215 WebSocketService.Instance.banFromCommunity(form);
1217 let form: BanUserForm = {
1218 user_id: i.props.post.creator_id,
1219 ban: !i.props.post.banned,
1220 reason: i.state.banReason,
1221 expires: getUnixTime(i.state.banExpires),
1223 WebSocketService.Instance.banUser(form);
1226 i.state.showBanDialog = false;
1227 i.setState(i.state);
1230 handleAddModToCommunity(i: PostListing) {
1231 let form: AddModToCommunityForm = {
1232 user_id: i.props.post.creator_id,
1233 community_id: i.props.post.community_id,
1236 WebSocketService.Instance.addModToCommunity(form);
1237 i.setState(i.state);
1240 handleAddAdmin(i: PostListing) {
1241 let form: AddAdminForm = {
1242 user_id: i.props.post.creator_id,
1245 WebSocketService.Instance.addAdmin(form);
1246 i.setState(i.state);
1249 handleShowConfirmTransferCommunity(i: PostListing) {
1250 i.state.showConfirmTransferCommunity = true;
1251 i.setState(i.state);
1254 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1255 i.state.showConfirmTransferCommunity = false;
1256 i.setState(i.state);
1259 handleTransferCommunity(i: PostListing) {
1260 let form: TransferCommunityForm = {
1261 community_id: i.props.post.community_id,
1262 user_id: i.props.post.creator_id,
1264 WebSocketService.Instance.transferCommunity(form);
1265 i.state.showConfirmTransferCommunity = false;
1266 i.setState(i.state);
1269 handleShowConfirmTransferSite(i: PostListing) {
1270 i.state.showConfirmTransferSite = true;
1271 i.setState(i.state);
1274 handleCancelShowConfirmTransferSite(i: PostListing) {
1275 i.state.showConfirmTransferSite = false;
1276 i.setState(i.state);
1279 handleTransferSite(i: PostListing) {
1280 let form: TransferSiteForm = {
1281 user_id: i.props.post.creator_id,
1283 WebSocketService.Instance.transferSite(form);
1284 i.state.showConfirmTransferSite = false;
1285 i.setState(i.state);
1288 handleImageExpandClick(i: PostListing) {
1289 i.state.imageExpanded = !i.state.imageExpanded;
1290 i.setState(i.state);
1293 handleViewSource(i: PostListing) {
1294 i.state.viewSource = !i.state.viewSource;
1295 i.setState(i.state);
1298 handleShowAdvanced(i: PostListing) {
1299 i.state.showAdvanced = !i.state.showAdvanced;
1300 i.setState(i.state);
1304 get pointsTippy(): string {
1305 let points = i18n.t('number_of_points', {
1306 count: this.state.score,
1309 let upvotes = i18n.t('number_of_upvotes', {
1310 count: this.state.upvotes,
1313 let downvotes = i18n.t('number_of_downvotes', {
1314 count: this.state.downvotes,
1317 return `${points} • ${upvotes} • ${downvotes}`;