1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { WebSocketService, UserService } from '../services';
19 CommunityModeratorView,
20 } from 'lemmy-js-client';
21 import { BanType } 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';
27 import { PictrsImage } from './pictrs-image';
40 import { i18n } from '../i18next';
41 import { externalHost } from '../env';
43 interface PostListingState {
45 showRemoveDialog: boolean;
47 showBanDialog: boolean;
52 showConfirmTransferSite: boolean;
53 showConfirmTransferCommunity: boolean;
54 imageExpanded: boolean;
56 showAdvanced: boolean;
57 showMoreMobile: boolean;
64 interface PostListingProps {
66 duplicates?: PostView[];
67 showCommunity?: boolean;
69 moderators?: CommunityModeratorView[];
70 admins?: UserViewSafe[];
71 enableDownvotes: boolean;
75 export class PostListing extends Component<PostListingProps, PostListingState> {
76 private emptyState: PostListingState = {
78 showRemoveDialog: false,
84 banType: BanType.Community,
85 showConfirmTransferSite: false,
86 showConfirmTransferCommunity: false,
90 showMoreMobile: false,
91 my_vote: this.props.post_view.my_vote,
92 score: this.props.post_view.counts.score,
93 upvotes: this.props.post_view.counts.upvotes,
94 downvotes: this.props.post_view.counts.downvotes,
97 constructor(props: any, context: any) {
98 super(props, context);
100 this.state = this.emptyState;
101 this.handlePostLike = this.handlePostLike.bind(this);
102 this.handlePostDisLike = this.handlePostDisLike.bind(this);
103 this.handleEditPost = this.handleEditPost.bind(this);
104 this.handleEditCancel = this.handleEditCancel.bind(this);
107 componentWillReceiveProps(nextProps: PostListingProps) {
108 this.state.my_vote = nextProps.post_view.my_vote;
109 this.state.upvotes = nextProps.post_view.counts.upvotes;
110 this.state.downvotes = nextProps.post_view.counts.downvotes;
111 this.state.score = nextProps.post_view.counts.score;
112 if (this.props.post_view.post.id !== nextProps.post_view.post.id) {
113 this.state.imageExpanded = false;
115 this.setState(this.state);
121 {!this.state.showEdit ? (
129 post_view={this.props.post_view}
130 onEdit={this.handleEditPost}
131 onCancel={this.handleEditCancel}
132 enableNsfw={this.props.enableNsfw}
133 enableDownvotes={this.props.enableDownvotes}
142 let post = this.props.post_view.post;
146 {post.url && this.props.showBody && post.embed_title && (
147 <IFramelyCard post={post} />
149 {this.props.showBody &&
151 (this.state.viewSource ? (
152 <pre>{post.body}</pre>
156 dangerouslySetInnerHTML={mdToHtml(post.body)}
164 imgThumb(src: string) {
165 let post_view = this.props.post_view;
170 nsfw={post_view.post.nsfw || post_view.community.nsfw}
175 getImageSrc(): string {
176 let post = this.props.post_view.post;
177 if (isImage(post.url)) {
178 if (post.url.includes('pictrs')) {
180 } else if (post.thumbnail_url) {
181 return post.thumbnail_url;
185 } else if (post.thumbnail_url) {
186 return post.thumbnail_url;
193 let post = this.props.post_view.post;
195 if (isImage(post.url)) {
198 class="float-right text-body pointer d-inline-block position-relative mb-2"
199 data-tippy-content={i18n.t('expand_here')}
200 onClick={linkEvent(this, this.handleImageExpandClick)}
202 {this.imgThumb(this.getImageSrc())}
203 <svg class="icon mini-overlay">
204 <use xlinkHref="#icon-image"></use>
208 } else if (post.thumbnail_url) {
211 class="float-right text-body d-inline-block position-relative mb-2"
217 {this.imgThumb(this.getImageSrc())}
218 <svg class="icon mini-overlay">
219 <use xlinkHref="#icon-external-link"></use>
223 } else if (post.url) {
224 if (isVideo(post.url)) {
226 <div class="embed-responsive embed-responsive-16by9">
232 class="embed-responsive-item"
234 <source src={post.url} type="video/mp4" />
241 className="text-body"
247 <div class="thumbnail rounded bg-light d-flex justify-content-center">
248 <svg class="icon d-flex align-items-center">
249 <use xlinkHref="#icon-external-link"></use>
258 className="text-body"
259 to={`/post/${post.id}`}
260 title={i18n.t('comments')}
262 <div class="thumbnail rounded bg-light d-flex justify-content-center">
263 <svg class="icon d-flex align-items-center">
264 <use xlinkHref="#icon-message-square"></use>
273 let post_view = this.props.post_view;
275 <ul class="list-inline mb-1 text-muted small">
276 <li className="list-inline-item">
277 <UserListing user={post_view.creator} />
280 <span className="mx-1 badge badge-light">{i18n.t('mod')}</span>
283 <span className="mx-1 badge badge-light">{i18n.t('admin')}</span>
285 {(post_view.creator_banned_from_community ||
286 post_view.creator.banned) && (
287 <span className="mx-1 badge badge-danger">{i18n.t('banned')}</span>
289 {this.props.showCommunity && (
291 <span class="mx-1"> {i18n.t('to')} </span>
292 <CommunityLink community={post_view.community} />
296 <li className="list-inline-item">•</li>
297 {post_view.post.url && !(hostname(post_view.post.url) == externalHost) && (
299 <li className="list-inline-item">
301 className="text-muted font-italic"
302 href={post_view.post.url}
304 title={post_view.post.url}
307 {hostname(post_view.post.url)}
310 <li className="list-inline-item">•</li>
313 <li className="list-inline-item">
315 <MomentTime data={post_view.post} />
318 {post_view.post.body && (
320 <li className="list-inline-item">•</li>
321 <li className="list-inline-item">
322 {/* Using a link with tippy doesn't work on touch devices unfortunately */}
324 className="text-muted"
325 data-tippy-content={md.render(
326 previewLines(post_view.post.body)
328 data-tippy-allowHtml={true}
329 to={`/post/${post_view.post.id}`}
331 <svg class="mr-1 icon icon-inline">
332 <use xlinkHref="#icon-book-open"></use>
344 <div className={`vote-bar col-1 pr-0 small text-center`}>
346 className={`btn-animate btn btn-link p-0 ${
347 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
349 onClick={linkEvent(this, this.handlePostLike)}
350 data-tippy-content={i18n.t('upvote')}
352 <svg class="icon upvote">
353 <use xlinkHref="#icon-arrow-up1"></use>
357 class={`unselectable pointer font-weight-bold text-muted px-1`}
358 data-tippy-content={this.pointsTippy}
362 {this.props.enableDownvotes && (
364 className={`btn-animate btn btn-link p-0 ${
365 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
367 onClick={linkEvent(this, this.handlePostDisLike)}
368 data-tippy-content={i18n.t('downvote')}
370 <svg class="icon downvote">
371 <use xlinkHref="#icon-arrow-down1"></use>
380 let post = this.props.post_view.post;
382 <div className="post-title overflow-hidden">
384 {this.props.showBody && post.url ? (
386 className={!post.stickied ? 'text-body' : 'text-primary'}
396 className={!post.stickied ? 'text-body' : 'text-primary'}
397 to={`/post/${post.id}`}
398 title={i18n.t('comments')}
403 {(isImage(post.url) || post.thumbnail_url) &&
404 (!this.state.imageExpanded ? (
406 class="text-monospace unselectable pointer ml-2 text-muted small"
407 data-tippy-content={i18n.t('expand_here')}
408 onClick={linkEvent(this, this.handleImageExpandClick)}
410 <svg class="icon icon-inline">
411 <use xlinkHref="#icon-plus-square"></use>
417 class="text-monospace unselectable pointer ml-2 text-muted small"
418 onClick={linkEvent(this, this.handleImageExpandClick)}
420 <svg class="icon icon-inline">
421 <use xlinkHref="#icon-minus-square"></use>
427 onClick={linkEvent(this, this.handleImageExpandClick)}
429 <PictrsImage src={this.getImageSrc()} />
435 <small className="ml-2 text-muted font-italic">
441 className="unselectable pointer ml-2 text-muted font-italic"
442 data-tippy-content={i18n.t('deleted')}
444 <svg class={`icon icon-inline text-danger`}>
445 <use xlinkHref="#icon-trash"></use>
451 className="unselectable pointer ml-2 text-muted font-italic"
452 data-tippy-content={i18n.t('locked')}
454 <svg class={`icon icon-inline text-danger`}>
455 <use xlinkHref="#icon-lock"></use>
461 className="unselectable pointer ml-2 text-muted font-italic"
462 data-tippy-content={i18n.t('stickied')}
464 <svg class={`icon icon-inline text-primary`}>
465 <use xlinkHref="#icon-pin"></use>
470 <small className="ml-2 text-muted font-italic">
479 commentsLine(mobile: boolean = false) {
480 let post_view = this.props.post_view;
482 <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold mb-1">
483 <button class="btn btn-link text-muted p-0">
485 className="text-muted small"
486 title={i18n.t('number_of_comments', {
487 count: post_view.counts.comments,
489 to={`/post/${post_view.post.id}`}
491 <svg class="mr-1 icon icon-inline">
492 <use xlinkHref="#icon-message-square"></use>
494 {i18n.t('number_of_comments', {
495 count: post_view.counts.comments,
501 {this.state.downvotes !== 0 && (
503 class="btn text-muted py-0 pr-0"
504 data-tippy-content={this.pointsTippy}
507 <svg class="icon icon-inline mr-1">
508 <use xlinkHref="#icon-arrow-down1"></use>
510 <span>{this.state.downvotes}</span>
514 {!this.props.showBody && (
516 class="btn btn-link btn-animate text-muted py-0"
517 onClick={linkEvent(this, this.handleSavePostClick)}
519 post_view.saved ? i18n.t('unsave') : i18n.t('save')
524 class={`icon icon-inline ${
525 post_view.saved && 'text-warning'
528 <use xlinkHref="#icon-star"></use>
535 {/* This is an expanding spacer for mobile */}
536 <div className="flex-grow-1"></div>
541 className={`btn-animate btn py-0 px-1 ${
542 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
544 data-tippy-content={this.pointsTippy}
545 onClick={linkEvent(this, this.handlePostLike)}
547 <svg class="small icon icon-inline mr-2">
548 <use xlinkHref="#icon-arrow-up1"></use>
552 {this.props.enableDownvotes && (
554 className={`ml-2 btn-animate btn py-0 pl-1 ${
555 this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
557 onClick={linkEvent(this, this.handlePostDisLike)}
558 data-tippy-content={this.pointsTippy}
560 <svg class="small icon icon-inline mr-2">
561 <use xlinkHref="#icon-arrow-down1"></use>
563 {this.state.downvotes !== 0 && (
564 <span>{this.state.downvotes}</span>
570 class="btn btn-link btn-animate text-muted py-0 pl-1 pr-0"
571 onClick={linkEvent(this, this.handleSavePostClick)}
573 post_view.saved ? i18n.t('unsave') : i18n.t('save')
577 class={`icon icon-inline ${post_view.saved && 'text-warning'}`}
579 <use xlinkHref="#icon-star"></use>
583 {!this.state.showMoreMobile && this.props.showBody && (
585 class="btn btn-link btn-animate text-muted py-0"
586 onClick={linkEvent(this, this.handleShowMoreMobile)}
587 data-tippy-content={i18n.t('more')}
589 <svg class="icon icon-inline">
590 <use xlinkHref="#icon-more-vertical"></use>
594 {this.state.showMoreMobile && this.postActions(mobile)}
603 this.props.duplicates && (
604 <ul class="list-inline mb-1 small text-muted">
606 <li className="list-inline-item mr-2">
607 {i18n.t('cross_posted_to')}
609 {this.props.duplicates.map(pv => (
610 <li className="list-inline-item mr-2">
611 <Link to={`/post/${pv.post.id}`}>
614 : `${pv.community.name}@${hostname(pv.community.actor_id)}`}
624 postActions(mobile: boolean = false) {
625 let post_view = this.props.post_view;
627 UserService.Instance.user && (
629 {this.props.showBody && (
633 class="btn btn-link btn-animate text-muted py-0 pl-0"
634 onClick={linkEvent(this, this.handleSavePostClick)}
636 post_view.saved ? i18n.t('unsave') : i18n.t('save')
640 class={`icon icon-inline ${
641 post_view.saved && 'text-warning'
644 <use xlinkHref="#icon-star"></use>
649 className="btn btn-link btn-animate text-muted py-0"
650 to={`/create_post${this.crossPostParams}`}
651 title={i18n.t('cross_post')}
653 <svg class="icon icon-inline">
654 <use xlinkHref="#icon-copy"></use>
659 {this.myPost && this.props.showBody && (
662 class="btn btn-link btn-animate text-muted py-0"
663 onClick={linkEvent(this, this.handleEditClick)}
664 data-tippy-content={i18n.t('edit')}
666 <svg class="icon icon-inline">
667 <use xlinkHref="#icon-edit"></use>
671 class="btn btn-link btn-animate text-muted py-0"
672 onClick={linkEvent(this, this.handleDeleteClick)}
674 !post_view.post.deleted ? i18n.t('delete') : i18n.t('restore')
678 class={`icon icon-inline ${
679 post_view.post.deleted && 'text-danger'
682 <use xlinkHref="#icon-trash"></use>
688 {!this.state.showAdvanced && this.props.showBody ? (
690 class="btn btn-link btn-animate text-muted py-0"
691 onClick={linkEvent(this, this.handleShowAdvanced)}
692 data-tippy-content={i18n.t('more')}
694 <svg class="icon icon-inline">
695 <use xlinkHref="#icon-more-vertical"></use>
700 {this.props.showBody && post_view.post.body && (
702 class="btn btn-link btn-animate text-muted py-0"
703 onClick={linkEvent(this, this.handleViewSource)}
704 data-tippy-content={i18n.t('view_source')}
707 class={`icon icon-inline ${
708 this.state.viewSource && 'text-success'
711 <use xlinkHref="#icon-file-text"></use>
715 {this.canModOnSelf && (
718 class="btn btn-link btn-animate text-muted py-0"
719 onClick={linkEvent(this, this.handleModLock)}
721 post_view.post.locked ? i18n.t('unlock') : i18n.t('lock')
725 class={`icon icon-inline ${
726 post_view.post.locked && 'text-danger'
729 <use xlinkHref="#icon-lock"></use>
733 class="btn btn-link btn-animate text-muted py-0"
734 onClick={linkEvent(this, this.handleModSticky)}
736 post_view.post.stickied
742 class={`icon icon-inline ${
743 post_view.post.stickied && 'text-success'
746 <use xlinkHref="#icon-pin"></use>
751 {/* Mods can ban from community, and appoint as mods to community */}
752 {(this.canMod || this.canAdmin) &&
753 (!post_view.post.removed ? (
755 class="btn btn-link btn-animate text-muted py-0"
756 onClick={linkEvent(this, this.handleModRemoveShow)}
762 class="btn btn-link btn-animate text-muted py-0"
763 onClick={linkEvent(this, this.handleModRemoveSubmit)}
771 (!post_view.creator_banned_from_community ? (
773 class="btn btn-link btn-animate text-muted py-0"
776 this.handleModBanFromCommunityShow
783 class="btn btn-link btn-animate text-muted py-0"
786 this.handleModBanFromCommunitySubmit
792 {!post_view.creator_banned_from_community &&
793 post_view.creator.local && (
795 class="btn btn-link btn-animate text-muted py-0"
796 onClick={linkEvent(this, this.handleAddModToCommunity)}
799 ? i18n.t('remove_as_mod')
800 : i18n.t('appoint_as_mod')}
805 {/* Community creators and admins can transfer community to another mod */}
806 {(this.amCommunityCreator || this.canAdmin) &&
808 post_view.creator.local &&
809 (!this.state.showConfirmTransferCommunity ? (
811 class="btn btn-link btn-animate text-muted py-0"
814 this.handleShowConfirmTransferCommunity
817 {i18n.t('transfer_community')}
821 <button class="d-inline-block mr-1 btn btn-link btn-animate text-muted py-0">
822 {i18n.t('are_you_sure')}
825 class="btn btn-link btn-animate text-muted py-0 d-inline-block mr-1"
826 onClick={linkEvent(this, this.handleTransferCommunity)}
831 class="btn btn-link btn-animate text-muted py-0 d-inline-block"
834 this.handleCancelShowConfirmTransferCommunity
841 {/* Admins can ban from all, and appoint other admins */}
845 (!post_view.creator.banned ? (
847 class="btn btn-link btn-animate text-muted py-0"
848 onClick={linkEvent(this, this.handleModBanShow)}
850 {i18n.t('ban_from_site')}
854 class="btn btn-link btn-animate text-muted py-0"
855 onClick={linkEvent(this, this.handleModBanSubmit)}
857 {i18n.t('unban_from_site')}
860 {!post_view.creator.banned && post_view.creator.local && (
862 class="btn btn-link btn-animate text-muted py-0"
863 onClick={linkEvent(this, this.handleAddAdmin)}
866 ? i18n.t('remove_as_admin')
867 : i18n.t('appoint_as_admin')}
872 {/* Site Creator can transfer to another admin */}
873 {this.amSiteCreator &&
875 (!this.state.showConfirmTransferSite ? (
877 class="btn btn-link btn-animate text-muted py-0"
880 this.handleShowConfirmTransferSite
883 {i18n.t('transfer_site')}
887 <button class="btn btn-link btn-animate text-muted py-0 d-inline-block mr-1">
888 {i18n.t('are_you_sure')}
891 class="btn btn-link btn-animate text-muted py-0 d-inline-block mr-1"
892 onClick={linkEvent(this, this.handleTransferSite)}
897 class="btn btn-link btn-animate text-muted py-0 d-inline-block"
900 this.handleCancelShowConfirmTransferSite
914 removeAndBanDialogs() {
915 let post = this.props.post_view;
918 {this.state.showRemoveDialog && (
921 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
925 class="form-control mr-2"
926 placeholder={i18n.t('reason')}
927 value={this.state.removeReason}
928 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
930 <button type="submit" class="btn btn-secondary">
931 {i18n.t('remove_post')}
935 {this.state.showBanDialog && (
936 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
937 <div class="form-group row">
938 <label class="col-form-label" htmlFor="post-listing-reason">
943 id="post-listing-reason"
944 class="form-control mr-2"
945 placeholder={i18n.t('reason')}
946 value={this.state.banReason}
947 onInput={linkEvent(this, this.handleModBanReasonChange)}
949 <div class="form-group">
950 <div class="form-check">
952 class="form-check-input"
953 id="mod-ban-remove-data"
955 checked={this.state.removeData}
956 onChange={linkEvent(this, this.handleModRemoveDataChange)}
958 <label class="form-check-label" htmlFor="mod-ban-remove-data">
959 {i18n.t('remove_posts_comments')}
964 {/* TODO hold off on expires until later */}
965 {/* <div class="form-group row"> */}
966 {/* <label class="col-form-label">Expires</label> */}
967 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
969 <div class="form-group row">
970 <button type="submit" class="btn btn-secondary">
971 {i18n.t('ban')} {post.creator.name}
981 let post = this.props.post_view.post;
982 return post.thumbnail_url || isImage(post.url) ? (
984 <div className={`${this.state.imageExpanded ? 'col-12' : 'col-8'}`}>
985 {this.postTitleLine()}
988 {/* Post body prev or thumbnail */}
989 {!this.state.imageExpanded && this.thumbnail()}
997 showMobilePreview() {
998 let post = this.props.post_view.post;
1001 !this.props.showBody && (
1003 className="md-div mb-1"
1004 dangerouslySetInnerHTML={{
1005 __html: md.render(previewLines(post.body)),
1015 {/* The mobile view*/}
1016 <div class="d-block d-sm-none">
1018 <div class="col-12">
1019 {this.createdLine()}
1021 {/* If it has a thumbnail, do a right aligned thumbnail */}
1022 {this.mobileThumbnail()}
1024 {/* Show a preview of the post body */}
1025 {this.showMobilePreview()}
1027 {this.commentsLine(true)}
1028 {this.duplicatesLine()}
1029 {this.removeAndBanDialogs()}
1034 {/* The larger view*/}
1035 <div class="d-none d-sm-block">
1038 {!this.state.imageExpanded && (
1039 <div class="col-sm-2 pr-0">
1040 <div class="">{this.thumbnail()}</div>
1045 this.state.imageExpanded ? 'col-12' : 'col-12 col-sm-9'
1049 <div className="col-12">
1050 {this.postTitleLine()}
1051 {this.createdLine()}
1052 {this.commentsLine()}
1053 {this.duplicatesLine()}
1054 {this.postActions()}
1055 {this.removeAndBanDialogs()}
1065 private get myPost(): boolean {
1067 UserService.Instance.user &&
1068 this.props.post_view.creator.id == UserService.Instance.user.id
1072 get isMod(): boolean {
1074 this.props.moderators &&
1076 this.props.moderators.map(m => m.moderator.id),
1077 this.props.post_view.creator.id
1082 get isAdmin(): boolean {
1084 this.props.admins &&
1086 this.props.admins.map(a => a.user.id),
1087 this.props.post_view.creator.id
1092 get canMod(): boolean {
1093 if (this.props.admins && this.props.moderators) {
1094 let adminsThenMods = this.props.admins
1095 .map(a => a.user.id)
1096 .concat(this.props.moderators.map(m => m.moderator.id));
1099 UserService.Instance.user,
1101 this.props.post_view.creator.id
1108 get canModOnSelf(): boolean {
1109 if (this.props.admins && this.props.moderators) {
1110 let adminsThenMods = this.props.admins
1111 .map(a => a.user.id)
1112 .concat(this.props.moderators.map(m => m.moderator.id));
1115 UserService.Instance.user,
1117 this.props.post_view.creator.id,
1125 get canAdmin(): boolean {
1127 this.props.admins &&
1129 UserService.Instance.user,
1130 this.props.admins.map(a => a.user.id),
1131 this.props.post_view.creator.id
1136 get amCommunityCreator(): boolean {
1138 this.props.moderators &&
1139 UserService.Instance.user &&
1140 this.props.post_view.creator.id != UserService.Instance.user.id &&
1141 UserService.Instance.user.id == this.props.moderators[0].moderator.id
1145 get amSiteCreator(): boolean {
1147 this.props.admins &&
1148 UserService.Instance.user &&
1149 this.props.post_view.creator.id != UserService.Instance.user.id &&
1150 UserService.Instance.user.id == this.props.admins[0].user.id
1154 handlePostLike(i: PostListing) {
1155 if (!UserService.Instance.user) {
1156 this.context.router.history.push(`/login`);
1159 let new_vote = i.state.my_vote == 1 ? 0 : 1;
1161 if (i.state.my_vote == 1) {
1164 } else if (i.state.my_vote == -1) {
1165 i.state.downvotes--;
1173 i.state.my_vote = new_vote;
1175 let form: CreatePostLike = {
1176 post_id: i.props.post_view.post.id,
1177 score: i.state.my_vote,
1178 auth: UserService.Instance.authField(),
1181 WebSocketService.Instance.client.likePost(form);
1182 i.setState(i.state);
1186 handlePostDisLike(i: PostListing) {
1187 if (!UserService.Instance.user) {
1188 this.context.router.history.push(`/login`);
1191 let new_vote = i.state.my_vote == -1 ? 0 : -1;
1193 if (i.state.my_vote == 1) {
1196 i.state.downvotes++;
1197 } else if (i.state.my_vote == -1) {
1198 i.state.downvotes--;
1201 i.state.downvotes++;
1205 i.state.my_vote = new_vote;
1207 let form: CreatePostLike = {
1208 post_id: i.props.post_view.post.id,
1209 score: i.state.my_vote,
1210 auth: UserService.Instance.authField(),
1213 WebSocketService.Instance.client.likePost(form);
1214 i.setState(i.state);
1218 handleEditClick(i: PostListing) {
1219 i.state.showEdit = true;
1220 i.setState(i.state);
1223 handleEditCancel() {
1224 this.state.showEdit = false;
1225 this.setState(this.state);
1228 // The actual editing is done in the recieve for post
1230 this.state.showEdit = false;
1231 this.setState(this.state);
1234 handleDeleteClick(i: PostListing) {
1235 let deleteForm: DeletePost = {
1236 edit_id: i.props.post_view.post.id,
1237 deleted: !i.props.post_view.post.deleted,
1238 auth: UserService.Instance.authField(),
1240 WebSocketService.Instance.client.deletePost(deleteForm);
1243 handleSavePostClick(i: PostListing) {
1245 i.props.post_view.saved == undefined ? true : !i.props.post_view.saved;
1246 let form: SavePost = {
1247 post_id: i.props.post_view.post.id,
1249 auth: UserService.Instance.authField(),
1252 WebSocketService.Instance.client.savePost(form);
1255 get crossPostParams(): string {
1256 let post = this.props.post_view.post;
1257 let params = `?title=${post.name}`;
1260 params += `&url=${encodeURIComponent(post.url)}`;
1263 params += `&body=${post.body}`;
1268 handleModRemoveShow(i: PostListing) {
1269 i.state.showRemoveDialog = true;
1270 i.setState(i.state);
1273 handleModRemoveReasonChange(i: PostListing, event: any) {
1274 i.state.removeReason = event.target.value;
1275 i.setState(i.state);
1278 handleModRemoveDataChange(i: PostListing, event: any) {
1279 i.state.removeData = event.target.checked;
1280 i.setState(i.state);
1283 handleModRemoveSubmit(i: PostListing) {
1284 event.preventDefault();
1285 let form: RemovePost = {
1286 edit_id: i.props.post_view.post.id,
1287 removed: !i.props.post_view.post.removed,
1288 reason: i.state.removeReason,
1289 auth: UserService.Instance.authField(),
1291 WebSocketService.Instance.client.removePost(form);
1293 i.state.showRemoveDialog = false;
1294 i.setState(i.state);
1297 handleModLock(i: PostListing) {
1298 let form: LockPost = {
1299 edit_id: i.props.post_view.post.id,
1300 locked: !i.props.post_view.post.locked,
1301 auth: UserService.Instance.authField(),
1303 WebSocketService.Instance.client.lockPost(form);
1306 handleModSticky(i: PostListing) {
1307 let form: StickyPost = {
1308 edit_id: i.props.post_view.post.id,
1309 stickied: !i.props.post_view.post.stickied,
1310 auth: UserService.Instance.authField(),
1312 WebSocketService.Instance.client.stickyPost(form);
1315 handleModBanFromCommunityShow(i: PostListing) {
1316 i.state.showBanDialog = true;
1317 i.state.banType = BanType.Community;
1318 i.setState(i.state);
1321 handleModBanShow(i: PostListing) {
1322 i.state.showBanDialog = true;
1323 i.state.banType = BanType.Site;
1324 i.setState(i.state);
1327 handleModBanReasonChange(i: PostListing, event: any) {
1328 i.state.banReason = event.target.value;
1329 i.setState(i.state);
1332 handleModBanExpiresChange(i: PostListing, event: any) {
1333 i.state.banExpires = event.target.value;
1334 i.setState(i.state);
1337 handleModBanFromCommunitySubmit(i: PostListing) {
1338 i.state.banType = BanType.Community;
1339 i.setState(i.state);
1340 i.handleModBanBothSubmit(i);
1343 handleModBanSubmit(i: PostListing) {
1344 i.state.banType = BanType.Site;
1345 i.setState(i.state);
1346 i.handleModBanBothSubmit(i);
1349 handleModBanBothSubmit(i: PostListing) {
1350 event.preventDefault();
1352 if (i.state.banType == BanType.Community) {
1353 // If its an unban, restore all their data
1354 let ban = !i.props.post_view.creator_banned_from_community;
1356 i.state.removeData = false;
1358 let form: BanFromCommunity = {
1359 user_id: i.props.post_view.creator.id,
1360 community_id: i.props.post_view.community.id,
1362 remove_data: i.state.removeData,
1363 reason: i.state.banReason,
1364 expires: getUnixTime(i.state.banExpires),
1365 auth: UserService.Instance.authField(),
1367 WebSocketService.Instance.client.banFromCommunity(form);
1369 // If its an unban, restore all their data
1370 let ban = !i.props.post_view.creator.banned;
1372 i.state.removeData = false;
1374 let form: BanUser = {
1375 user_id: i.props.post_view.creator.id,
1377 remove_data: i.state.removeData,
1378 reason: i.state.banReason,
1379 expires: getUnixTime(i.state.banExpires),
1380 auth: UserService.Instance.authField(),
1382 WebSocketService.Instance.client.banUser(form);
1385 i.state.showBanDialog = false;
1386 i.setState(i.state);
1389 handleAddModToCommunity(i: PostListing) {
1390 let form: AddModToCommunity = {
1391 user_id: i.props.post_view.creator.id,
1392 community_id: i.props.post_view.community.id,
1394 auth: UserService.Instance.authField(),
1396 WebSocketService.Instance.client.addModToCommunity(form);
1397 i.setState(i.state);
1400 handleAddAdmin(i: PostListing) {
1401 let form: AddAdmin = {
1402 user_id: i.props.post_view.creator.id,
1404 auth: UserService.Instance.authField(),
1406 WebSocketService.Instance.client.addAdmin(form);
1407 i.setState(i.state);
1410 handleShowConfirmTransferCommunity(i: PostListing) {
1411 i.state.showConfirmTransferCommunity = true;
1412 i.setState(i.state);
1415 handleCancelShowConfirmTransferCommunity(i: PostListing) {
1416 i.state.showConfirmTransferCommunity = false;
1417 i.setState(i.state);
1420 handleTransferCommunity(i: PostListing) {
1421 let form: TransferCommunity = {
1422 community_id: i.props.post_view.community.id,
1423 user_id: i.props.post_view.creator.id,
1424 auth: UserService.Instance.authField(),
1426 WebSocketService.Instance.client.transferCommunity(form);
1427 i.state.showConfirmTransferCommunity = false;
1428 i.setState(i.state);
1431 handleShowConfirmTransferSite(i: PostListing) {
1432 i.state.showConfirmTransferSite = true;
1433 i.setState(i.state);
1436 handleCancelShowConfirmTransferSite(i: PostListing) {
1437 i.state.showConfirmTransferSite = false;
1438 i.setState(i.state);
1441 handleTransferSite(i: PostListing) {
1442 let form: TransferSite = {
1443 user_id: i.props.post_view.creator.id,
1444 auth: UserService.Instance.authField(),
1446 WebSocketService.Instance.client.transferSite(form);
1447 i.state.showConfirmTransferSite = false;
1448 i.setState(i.state);
1451 handleImageExpandClick(i: PostListing) {
1452 i.state.imageExpanded = !i.state.imageExpanded;
1453 i.setState(i.state);
1456 handleViewSource(i: PostListing) {
1457 i.state.viewSource = !i.state.viewSource;
1458 i.setState(i.state);
1461 handleShowAdvanced(i: PostListing) {
1462 i.state.showAdvanced = !i.state.showAdvanced;
1463 i.setState(i.state);
1467 handleShowMoreMobile(i: PostListing) {
1468 i.state.showMoreMobile = !i.state.showMoreMobile;
1469 i.state.showAdvanced = !i.state.showAdvanced;
1470 i.setState(i.state);
1474 get pointsTippy(): string {
1475 let points = i18n.t('number_of_points', {
1476 count: this.state.score,
1479 let upvotes = i18n.t('number_of_upvotes', {
1480 count: this.state.upvotes,
1483 let downvotes = i18n.t('number_of_downvotes', {
1484 count: this.state.downvotes,
1487 return `${points} • ${upvotes} • ${downvotes}`;