1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { WebSocketService, UserService } from '../services';
4 import { Post, CreatePostLikeForm, PostForm as PostFormI, SavePostForm, CommunityUser, UserView } from '../interfaces';
5 import { MomentTime } from './moment-time';
6 import { PostForm } from './post-form';
7 import { mdToHtml, canMod, isMod, isImage } from '../utils';
9 interface PostListingState {
11 showRemoveDialog: boolean;
13 imageExpanded: boolean;
16 interface PostListingProps {
19 showCommunity?: boolean;
22 moderators?: Array<CommunityUser>;
23 admins?: Array<UserView>;
26 export class PostListing extends Component<PostListingProps, PostListingState> {
28 private emptyState: PostListingState = {
30 showRemoveDialog: false,
35 constructor(props: any, context: any) {
36 super(props, context);
38 this.state = this.emptyState;
39 this.handlePostLike = this.handlePostLike.bind(this);
40 this.handlePostDisLike = this.handlePostDisLike.bind(this);
41 this.handleEditPost = this.handleEditPost.bind(this);
42 this.handleEditCancel = this.handleEditCancel.bind(this);
50 : <PostForm post={this.props.post} onEdit={this.handleEditPost} onCancel={this.handleEditCancel}/>
57 let post = this.props.post;
60 <div className={`mr-1 float-left small text-center ${this.props.viewOnly && 'no-click'}`}>
61 <div className={`pointer ${post.my_vote == 1 ? 'text-info' : 'text-muted'}`} onClick={linkEvent(this, this.handlePostLike)}>
62 <svg class="icon upvote"><use xlinkHref="#icon-arrow-up"></use></svg>
64 <div class={`font-weight-bold text-muted`}>{post.score}</div>
65 <div className={`pointer ${post.my_vote == -1 ? 'text-danger' : 'text-muted'}`} onClick={linkEvent(this, this.handlePostDisLike)}>
66 <svg class="icon downvote"><use xlinkHref="#icon-arrow-down"></use></svg>
69 {post.url && isImage(post.url) &&
70 <span title="Expand here" class="pointer" onClick={linkEvent(this, this.handleImageExpandClick)}><img class="mx-2 float-left img-fluid thumbnail rounded" src={post.url} /></span>
72 <div className="ml-4">
74 <h5 className="mb-0 d-inline">
76 <a className="text-white" href={post.url} target="_blank" title={post.url}>{post.name}</a> :
77 <Link className="text-white" to={`/post/${post.id}`} title="Comments">{post.name}</Link>
82 <a className="ml-2 text-muted font-italic" href={post.url} target="_blank" title={post.url}>{(new URL(post.url)).hostname}</a>
86 <small className="ml-2 text-muted font-italic">removed</small>
89 <small className="ml-2 text-muted font-italic">deleted</small>
92 <small className="ml-2 text-muted font-italic">locked</small>
94 { post.url && isImage(post.url) &&
96 { !this.state.imageExpanded
97 ? <span class="badge badge-light pointer ml-2 text-muted small" title="Expand here" onClick={linkEvent(this, this.handleImageExpandClick)}>+</span>
100 <span class="pointer ml-2 badge badge-light text-muted small" onClick={linkEvent(this, this.handleImageExpandClick)}>-</span>
102 <span class="pointer" onClick={linkEvent(this, this.handleImageExpandClick)}><img class="img-fluid" src={post.url} /></span>
110 <div className="details ml-4 mb-1">
111 <ul class="list-inline mb-0 text-muted small">
112 <li className="list-inline-item">
114 <Link className="text-info" to={`/u/${post.creator_name}`}>{post.creator_name}</Link>
116 <span className="mx-1 badge badge-light">mod</span>
119 <span className="mx-1 badge badge-light">admin</span>
121 {this.props.showCommunity &&
124 <Link to={`/c/${post.community_name}`}>{post.community_name}</Link>
128 <li className="list-inline-item">
129 <span><MomentTime data={post} /></span>
131 <li className="list-inline-item">
133 <span className="text-info">+{post.upvotes}</span>
135 <span className="text-danger">-{post.downvotes}</span>
139 <li className="list-inline-item">
140 <Link className="text-muted" to={`/post/${post.id}`}>{post.number_of_comments} Comments</Link>
143 {UserService.Instance.user && this.props.editable &&
144 <ul class="list-inline mb-1 text-muted small font-weight-bold">
145 <li className="list-inline-item mr-2">
146 <span class="pointer" onClick={linkEvent(this, this.handleSavePostClick)}>{post.saved ? 'unsave' : 'save'}</span>
150 <li className="list-inline-item">
151 <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span>
153 <li className="list-inline-item mr-2">
154 <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>
155 {!post.deleted ? 'delete' : 'restore'}
162 <li className="list-inline-item">
163 {!this.props.post.removed ?
164 <span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> :
165 <span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span>
168 <li className="list-inline-item">
169 <span class="pointer" onClick={linkEvent(this, this.handleModLock)}>{this.props.post.locked ? 'unlock' : 'lock'}</span>
175 {this.state.showRemoveDialog &&
176 <form class="form-inline" onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
177 <input type="text" class="form-control mr-2" placeholder="Reason" value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} />
178 <button type="submit" class="btn btn-secondary">Remove Post</button>
181 {this.props.showBody && this.props.post.body && <div className="md-div" dangerouslySetInnerHTML={mdToHtml(post.body)} />}
187 private get myPost(): boolean {
188 return UserService.Instance.user && this.props.post.creator_id == UserService.Instance.user.id;
191 get isMod(): boolean {
192 return this.props.moderators && isMod(this.props.moderators.map(m => m.user_id), this.props.post.creator_id);
195 get isAdmin(): boolean {
196 return this.props.admins && isMod(this.props.admins.map(a => a.id), this.props.post.creator_id);
199 get canMod(): boolean {
201 if (this.props.editable) {
202 let adminsThenMods = this.props.admins.map(a => a.id)
203 .concat(this.props.moderators.map(m => m.user_id));
205 return canMod(UserService.Instance.user, adminsThenMods, this.props.post.creator_id);
210 handlePostLike(i: PostListing) {
212 let form: CreatePostLikeForm = {
213 post_id: i.props.post.id,
214 score: (i.props.post.my_vote == 1) ? 0 : 1
216 WebSocketService.Instance.likePost(form);
219 handlePostDisLike(i: PostListing) {
220 let form: CreatePostLikeForm = {
221 post_id: i.props.post.id,
222 score: (i.props.post.my_vote == -1) ? 0 : -1
224 WebSocketService.Instance.likePost(form);
227 handleEditClick(i: PostListing) {
228 i.state.showEdit = true;
233 this.state.showEdit = false;
234 this.setState(this.state);
237 // The actual editing is done in the recieve for post
239 this.state.showEdit = false;
240 this.setState(this.state);
243 handleDeleteClick(i: PostListing) {
244 let deleteForm: PostFormI = {
245 body: i.props.post.body,
246 community_id: i.props.post.community_id,
247 name: i.props.post.name,
248 url: i.props.post.url,
249 edit_id: i.props.post.id,
250 creator_id: i.props.post.creator_id,
251 deleted: !i.props.post.deleted,
254 WebSocketService.Instance.editPost(deleteForm);
257 handleSavePostClick(i: PostListing) {
258 let saved = (i.props.post.saved == undefined) ? true : !i.props.post.saved;
259 let form: SavePostForm = {
260 post_id: i.props.post.id,
264 WebSocketService.Instance.savePost(form);
267 handleModRemoveShow(i: PostListing) {
268 i.state.showRemoveDialog = true;
272 handleModRemoveReasonChange(i: PostListing, event: any) {
273 i.state.removeReason = event.target.value;
277 handleModRemoveSubmit(i: PostListing) {
278 event.preventDefault();
279 let form: PostFormI = {
280 name: i.props.post.name,
281 community_id: i.props.post.community_id,
282 edit_id: i.props.post.id,
283 creator_id: i.props.post.creator_id,
284 removed: !i.props.post.removed,
285 reason: i.state.removeReason,
288 WebSocketService.Instance.editPost(form);
290 i.state.showRemoveDialog = false;
294 handleModLock(i: PostListing) {
295 let form: PostFormI = {
296 name: i.props.post.name,
297 community_id: i.props.post.community_id,
298 edit_id: i.props.post.id,
299 creator_id: i.props.post.creator_id,
300 locked: !i.props.post.locked,
303 WebSocketService.Instance.editPost(form);
306 handleImageExpandClick(i: PostListing) {
307 i.state.imageExpanded = !i.state.imageExpanded;