diff options
author | Dessalines <tyhou13@gmx.com> | 2019-04-15 16:12:06 -0700 |
---|---|---|
committer | Dessalines <tyhou13@gmx.com> | 2019-04-15 16:12:06 -0700 |
commit | e94885eb97b3240ed9cec7f97d0f405b2819e922 (patch) | |
tree | b407f0b6ed9be27682e3767271e34933c947cb2a /ui | |
parent | 8590a612f633fe6ba8f8b18379a8a822a3b3019b (diff) |
Commiting before I lose everything. I'll do this properly in a merge
Diffstat (limited to 'ui')
-rw-r--r-- | ui/src/components/comment-form.tsx | 13 | ||||
-rw-r--r-- | ui/src/components/comment-node.tsx | 169 | ||||
-rw-r--r-- | ui/src/components/comment-nodes.tsx | 11 | ||||
-rw-r--r-- | ui/src/components/communities.tsx | 3 | ||||
-rw-r--r-- | ui/src/components/community.tsx | 6 | ||||
-rw-r--r-- | ui/src/components/modlog.tsx | 184 | ||||
-rw-r--r-- | ui/src/components/moment-time.tsx | 6 | ||||
-rw-r--r-- | ui/src/components/navbar.tsx | 3 | ||||
-rw-r--r-- | ui/src/components/post-form.tsx | 6 | ||||
-rw-r--r-- | ui/src/components/post-listing.tsx | 122 | ||||
-rw-r--r-- | ui/src/components/post.tsx | 22 | ||||
-rw-r--r-- | ui/src/components/sidebar.tsx | 151 | ||||
-rw-r--r-- | ui/src/components/user.tsx | 19 | ||||
-rw-r--r-- | ui/src/index.tsx | 2 | ||||
-rw-r--r-- | ui/src/interfaces.ts | 371 | ||||
-rw-r--r-- | ui/src/main.css | 5 | ||||
-rw-r--r-- | ui/src/services/WebSocketService.ts | 16 | ||||
-rw-r--r-- | ui/src/utils.ts | 8 | ||||
-rw-r--r-- | ui/src/version.ts | 2 |
19 files changed, 917 insertions, 202 deletions
diff --git a/ui/src/components/comment-form.tsx b/ui/src/components/comment-form.tsx index a87dd356..66f3094e 100644 --- a/ui/src/components/comment-form.tsx +++ b/ui/src/components/comment-form.tsx @@ -1,6 +1,6 @@ import { Component, linkEvent } from 'inferno'; import { CommentNode as CommentNodeI, CommentForm as CommentFormI } from '../interfaces'; -import { WebSocketService } from '../services'; +import { WebSocketService, UserService } from '../services'; import * as autosize from 'autosize'; interface CommentFormProps { @@ -8,6 +8,7 @@ interface CommentFormProps { node?: CommentNodeI; onReplyCancel?(): any; edit?: boolean; + disabled?: boolean; } interface CommentFormState { @@ -21,9 +22,10 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { commentForm: { auth: null, content: null, - post_id: this.props.node ? this.props.node.comment.post_id : this.props.postId + post_id: this.props.node ? this.props.node.comment.post_id : this.props.postId, + creator_id: UserService.Instance.loggedIn ? UserService.Instance.user.id : null, }, - buttonTitle: !this.props.node ? "Post" : this.props.edit ? "Edit" : "Reply" + buttonTitle: !this.props.node ? "Post" : this.props.edit ? "Edit" : "Reply", } constructor(props: any, context: any) { @@ -36,6 +38,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { this.state.commentForm.edit_id = this.props.node.comment.id; this.state.commentForm.parent_id = this.props.node.comment.parent_id; this.state.commentForm.content = this.props.node.comment.content; + this.state.commentForm.creator_id = this.props.node.comment.creator_id; } else { // A reply gets a new parent id this.state.commentForm.parent_id = this.props.node.comment.id; @@ -53,12 +56,12 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { <form onSubmit={linkEvent(this, this.handleCommentSubmit)}> <div class="form-group row"> <div class="col-sm-12"> - <textarea class="form-control" value={this.state.commentForm.content} onInput={linkEvent(this, this.handleCommentContentChange)} placeholder="Comment here" required /> + <textarea class="form-control" value={this.state.commentForm.content} onInput={linkEvent(this, this.handleCommentContentChange)} placeholder="Comment here" required disabled={this.props.disabled}/> </div> </div> <div class="row"> <div class="col-sm-12"> - <button type="submit" class="btn btn-sm btn-secondary mr-2">{this.state.buttonTitle}</button> + <button type="submit" class="btn btn-sm btn-secondary mr-2" disabled={this.props.disabled}>{this.state.buttonTitle}</button> {this.props.node && <button type="button" class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.handleReplyCancel)}>Cancel</button>} </div> </div> diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index 1e5376f2..eba36009 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -1,8 +1,8 @@ import { Component, linkEvent } from 'inferno'; import { Link } from 'inferno-router'; -import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI } from '../interfaces'; +import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI, BanFromCommunityForm, CommunityUser, AddModToCommunityForm } from '../interfaces'; import { WebSocketService, UserService } from '../services'; -import { mdToHtml } from '../utils'; +import { mdToHtml, getUnixTime } from '../utils'; import { MomentTime } from './moment-time'; import { CommentForm } from './comment-form'; import { CommentNodes } from './comment-nodes'; @@ -10,19 +10,31 @@ import { CommentNodes } from './comment-nodes'; interface CommentNodeState { showReply: boolean; showEdit: boolean; + showRemoveDialog: boolean; + removeReason: string; + showBanDialog: boolean; + banReason: string; + banExpires: string; } interface CommentNodeProps { node: CommentNodeI; noIndent?: boolean; viewOnly?: boolean; + locked?: boolean; + moderators: Array<CommunityUser>; } export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { private emptyState: CommentNodeState = { showReply: false, - showEdit: false + showEdit: false, + showRemoveDialog: false, + removeReason: null, + showBanDialog: false, + banReason: null, + banExpires: null, } constructor(props: any, context: any) { @@ -60,10 +72,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { <span><MomentTime data={node.comment} /></span> </li> </ul> - {this.state.showEdit && <CommentForm node={node} edit onReplyCancel={this.handleReplyCancel} />} + {this.state.showEdit && <CommentForm node={node} edit onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} />} {!this.state.showEdit && <div> - <div className="md-div" dangerouslySetInnerHTML={mdToHtml(node.comment.content)} /> + <div className="md-div" dangerouslySetInnerHTML={mdToHtml(node.comment.removed ? '*removed*' : node.comment.content)} /> <ul class="list-inline mb-1 text-muted small font-weight-bold"> {!this.props.viewOnly && <span class="mr-2"> @@ -71,14 +83,39 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { <span class="pointer" onClick={linkEvent(this, this.handleReplyClick)}>reply</span> </li> {this.myComment && + <> <li className="list-inline-item"> <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span> </li> - } - {this.myComment && <li className="list-inline-item"> <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span> </li> + </> + } + {this.canMod && + <> + <li className="list-inline-item"> + {!this.props.node.comment.removed ? + <span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> : + <span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span> + } + </li> + {!this.isMod && + <> + <li className="list-inline-item"> + {!this.props.node.comment.banned ? + <span class="pointer" onClick={linkEvent(this, this.handleModBanShow)}>ban</span> : + <span class="pointer" onClick={linkEvent(this, this.handleModBanSubmit)}>unban</span> + } + </li> + </> + } + {!this.props.node.comment.banned && + <li className="list-inline-item"> + <span class="pointer" onClick={linkEvent(this, this.handleAddModToCommunity)}>{`${this.isMod ? 'remove' : 'appoint'} as mod`}</span> + </li> + } + </> } </span> } @@ -89,16 +126,61 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { </div> } </div> - {this.state.showReply && <CommentForm node={node} onReplyCancel={this.handleReplyCancel} />} - {this.props.node.children && <CommentNodes nodes={this.props.node.children} />} + {this.state.showRemoveDialog && + <form class="form-inline" onSubmit={linkEvent(this, this.handleModRemoveSubmit)}> + <input type="text" class="form-control mr-2" placeholder="Reason" value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} /> + <button type="submit" class="btn btn-secondary">Remove Comment</button> + </form> + } + {this.state.showBanDialog && + <form onSubmit={linkEvent(this, this.handleModBanSubmit)}> + <div class="form-group row"> + <label class="col-form-label">Reason</label> + <input type="text" class="form-control mr-2" placeholder="Optional" value={this.state.banReason} onInput={linkEvent(this, this.handleModBanReasonChange)} /> + </div> + <div class="form-group row"> + <label class="col-form-label">Expires</label> + <input type="date" class="form-control mr-2" placeholder="Expires" value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> + </div> + <div class="form-group row"> + <button type="submit" class="btn btn-secondary">Ban {this.props.node.comment.creator_name}</button> + </div> + </form> + } + {this.state.showReply && <CommentForm node={node} onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} />} + {this.props.node.children && <CommentNodes nodes={this.props.node.children} locked={this.props.locked} moderators={this.props.moderators}/>} </div> ) } - private get myComment(): boolean { + get myComment(): boolean { return UserService.Instance.loggedIn && this.props.node.comment.creator_id == UserService.Instance.user.id; } + get canMod(): boolean { + + // You can do moderator actions only on the mods added after you. + if (UserService.Instance.loggedIn) { + let modIds = this.props.moderators.map(m => m.user_id); + let yourIndex = modIds.findIndex(id => id == UserService.Instance.user.id); + if (yourIndex == -1) { + return false; + } else { + console.log(modIds); + modIds = modIds.slice(0, yourIndex+1); // +1 cause you cant mod yourself + console.log(modIds); + return !modIds.includes(this.props.node.comment.creator_id); + } + } else { + return false; + } + + } + + get isMod(): boolean { + return this.props.moderators.map(m => m.user_id).includes(this.props.node.comment.creator_id); + } + handleReplyClick(i: CommentNode) { i.state.showReply = true; i.setState(i.state); @@ -113,6 +195,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { let deleteForm: CommentFormI = { content: "*deleted*", edit_id: i.props.node.comment.id, + creator_id: i.props.node.comment.creator_id, post_id: i.props.node.comment.post_id, parent_id: i.props.node.comment.parent_id, auth: null @@ -145,4 +228,70 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { }; WebSocketService.Instance.likeComment(form); } + + handleModRemoveShow(i: CommentNode) { + i.state.showRemoveDialog = true; + i.setState(i.state); + } + + handleModRemoveReasonChange(i: CommentNode, event: any) { + i.state.removeReason = event.target.value; + i.setState(i.state); + } + + handleModRemoveSubmit(i: CommentNode) { + let form: CommentFormI = { + content: i.props.node.comment.content, + edit_id: i.props.node.comment.id, + creator_id: i.props.node.comment.creator_id, + post_id: i.props.node.comment.post_id, + parent_id: i.props.node.comment.parent_id, + removed: !i.props.node.comment.removed, + reason: i.state.removeReason, + auth: null + }; + WebSocketService.Instance.editComment(form); + + i.state.showRemoveDialog = false; + i.setState(i.state); + } + + handleModBanShow(i: CommentNode) { + i.state.showBanDialog = true; + i.setState(i.state); + } + + handleModBanReasonChange(i: CommentNode, event: any) { + i.state.banReason = event.target.value; + i.setState(i.state); + } + + handleModBanExpiresChange(i: CommentNode, event: any) { + i.state.banExpires = event.target.value; + i.setState(i.state); + } + + handleModBanSubmit(i: CommentNode) { + let form: BanFromCommunityForm = { + user_id: i.props.node.comment.creator_id, + community_id: i.props.node.comment.community_id, + ban: !i.props.node.comment.banned, + reason: i.state.banReason, + expires: getUnixTime(i.state.banExpires), + }; + WebSocketService.Instance.banFromCommunity(form); + + i.state.showBanDialog = false; + i.setState(i.state); + } + + handleAddModToCommunity(i: CommentNode) { + let form: AddModToCommunityForm = { + user_id: i.props.node.comment.creator_id, + community_id: i.props.node.comment.community_id, + added: !i.isMod, + }; + WebSocketService.Instance.addModToCommunity(form); + i.setState(i.state); + } } diff --git a/ui/src/components/comment-nodes.tsx b/ui/src/components/comment-nodes.tsx index 76d5c57e..19ba30c6 100644 --- a/ui/src/components/comment-nodes.tsx +++ b/ui/src/components/comment-nodes.tsx @@ -1,5 +1,5 @@ import { Component } from 'inferno'; -import { CommentNode as CommentNodeI } from '../interfaces'; +import { CommentNode as CommentNodeI, CommunityUser } from '../interfaces'; import { CommentNode } from './comment-node'; interface CommentNodesState { @@ -7,8 +7,10 @@ interface CommentNodesState { interface CommentNodesProps { nodes: Array<CommentNodeI>; + moderators: Array<CommunityUser>; noIndent?: boolean; viewOnly?: boolean; + locked?: boolean; } export class CommentNodes extends Component<CommentNodesProps, CommentNodesState> { @@ -21,10 +23,15 @@ export class CommentNodes extends Component<CommentNodesProps, CommentNodesState return ( <div className="comments"> {this.props.nodes.map(node => - <CommentNode node={node} noIndent={this.props.noIndent} viewOnly={this.props.viewOnly}/> + <CommentNode node={node} + noIndent={this.props.noIndent} + viewOnly={this.props.viewOnly} + locked={this.props.locked} + moderators={this.props.moderators}/> )} </div> ) } + } diff --git a/ui/src/components/communities.tsx b/ui/src/components/communities.tsx index 4d2512cc..f8cce2c0 100644 --- a/ui/src/components/communities.tsx +++ b/ui/src/components/communities.tsx @@ -102,7 +102,6 @@ export class Communities extends Component<any, CommunitiesState> { WebSocketService.Instance.followCommunity(form); } - handleSubscribe(communityId: number) { let form: FollowCommunityForm = { community_id: communityId, @@ -129,6 +128,6 @@ export class Communities extends Component<any, CommunitiesState> { found.subscribed = res.community.subscribed; found.number_of_subscribers = res.community.number_of_subscribers; this.setState(this.state); - } + } } } diff --git a/ui/src/components/community.tsx b/ui/src/components/community.tsx index 13f6f68e..cd95f991 100644 --- a/ui/src/components/community.tsx +++ b/ui/src/components/community.tsx @@ -63,7 +63,11 @@ export class Community extends Component<any, State> { <h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> : <div class="row"> <div class="col-12 col-md-9"> - <h4>{this.state.community.title}</h4> + <h4>{this.state.community.title} + {this.state.community.removed && + <small className="ml-2 text-muted font-italic">removed</small> + } + </h4> <PostListings communityId={this.state.communityId} /> </div> <div class="col-12 col-md-3"> diff --git a/ui/src/components/modlog.tsx b/ui/src/components/modlog.tsx new file mode 100644 index 00000000..356cbc92 --- /dev/null +++ b/ui/src/components/modlog.tsx @@ -0,0 +1,184 @@ +import { Component } from 'inferno'; +import { Link } from 'inferno-router'; +import { Subscription } from "rxjs"; +import { retryWhen, delay, take } from 'rxjs/operators'; +import { UserOperation, GetModlogForm, GetModlogResponse, ModRemovePost, ModLockPost, ModRemoveComment, ModRemoveCommunity, ModBanFromCommunity, ModBan, ModAddCommunity, ModAdd } from '../interfaces'; +import { WebSocketService } from '../services'; +import { msgOp, addTypeInfo } from '../utils'; +import { MomentTime } from './moment-time'; +import * as moment from 'moment'; + +interface ModlogState { + removed_posts: Array<ModRemovePost>, + locked_posts: Array<ModLockPost>, + removed_comments: Array<ModRemoveComment>, + removed_communities: Array<ModRemoveCommunity>, + banned_from_community: Array<ModBanFromCommunity>, + banned: Array<ModBan>, + added_to_community: Array<ModAddCommunity>, + added: Array<ModAdd>, + loading: boolean; +} + +export class Modlog extends Component<any, ModlogState> { + private subscription: Subscription; + private emptyState: ModlogState = { + removed_posts: [], + locked_posts: [], + removed_comments: [], + removed_communities: [], + banned_from_community: [], + banned: [], + added_to_community: [], + added: [], + loading: true + } + + constructor(props: any, context: any) { + super(props, context); + this.state = this.emptyState; + this.subscription = WebSocketService.Instance.subject + .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) + .subscribe( + (msg) => this.parseMessage(msg), + (err) => console.error(err), + () => console.log('complete') + ); + + let modlogForm: GetModlogForm = { + + }; + WebSocketService.Instance.getModlog(modlogForm); + } + + componentWillUnmount() { + this.subscription.unsubscribe(); + } + + combined() { + let combined: Array<{type_: string, data: ModRemovePost | ModLockPost | ModRemoveCommunity}> = []; + let removed_posts = addTypeInfo(this.state.removed_posts, "removed_posts"); + let locked_posts = addTypeInfo(this.state.locked_posts, "locked_posts"); + let removed_comments = addTypeInfo(this.state.removed_comments, "removed_comments"); + let removed_communities = addTypeInfo(this.state.removed_communities, "removed_communities"); + let banned_from_community = addTypeInfo(this.state.banned_from_community, "banned_from_community"); + let added_to_community = addTypeInfo(this.state.added_to_community, "added_to_community"); + + combined.push(...removed_posts); + combined.push(...locked_posts); + combined.push(...removed_comments); + combined.push(...removed_communities); + combined.push(...banned_from_community); + combined.push(...added_to_community); + + // Sort them by time + combined.sort((a, b) => b.data.when_.localeCompare(a.data.when_)); + + console.log(combined); + + return ( + <tbody> + {combined.map(i => + <tr> + <td><MomentTime data={i.data} /></td> + <td><Link to={`/user/${i.data.mod_user_id}`}>{i.data.mod_user_name}</Link></td> + <td> + {i.type_ == 'removed_posts' && + <> + {(i.data as ModRemovePost).removed? 'Removed' : 'Restored'} + <span> Post <Link to={`/post/${(i.data as ModRemovePost).post_id}`}>{(i.data as ModRemovePost).post_name}</Link></span> + <div>{(i.data as ModRemovePost).reason && ` reason: ${(i.data as ModRemovePost).reason}`}</div> + </> + } + {i.type_ == 'locked_posts' && + <> + {(i.data as ModLockPost).locked? 'Locked' : 'Unlocked'} + <span> Post <Link to={`/post/${(i.data as ModLockPost).post_id}`}>{(i.data as ModLockPost).post_name}</Link></span> + </> + } + {i.type_ == 'removed_comments' && + <> + {(i.data as ModRemoveComment).removed? 'Removed' : 'Restored'} + <span> Comment <Link to={`/post/${(i.data as ModRemoveComment).post_id}/comment/${(i.data as ModRemoveComment).comment_id}`}>{(i.data as ModRemoveComment).comment_content}</Link></span> + <div>{(i.data as ModRemoveComment).reason && ` reason: ${(i.data as ModRemoveComment).reason}`}</div> + </> + } + {i.type_ == 'removed_communities' && + <> + {(i.data as ModRemoveCommunity).removed ? 'Removed' : 'Restored'} + <span> Community <Link to={`/community/${i.data.community_id}`}>{i.data.community_name}</Link></span> + <div>{(i.data as ModRemoveCommunity).reason && ` reason: ${(i.data as ModRemoveCommunity).reason}`}</div> + <div>{(i.data as ModRemoveCommunity).expires && ` expires: ${moment.utc((i.data as ModRemoveCommunity).expires).fromNow()}`}</div> + </> + } + {i.type_ == 'banned_from_community' && + <> + <span>{(i.data as ModBanFromCommunity).banned ? 'Banned ' : 'Unbanned '} </span> + <span><Link to={`/user/${(i.data as ModBanFromCommunity).other_user_id}`}>{(i.data as ModBanFromCommunity).other_user_name}</Link></span> + <div>{(i.data as ModBanFromCommunity).reason && ` reason: ${(i.data as ModBanFromCommunity).reason}`}</div> + <div>{(i.data as ModBanFromCommunity).expires && ` expires: ${moment.utc((i.data as ModBanFromCommunity).expires).fromNow()}`}</div> + </> + } + {i.type_ == 'added_to_community' && + <> + <span>{(i.data as ModAddCommunity).removed ? 'Removed ' : 'Appointed '} </span> + <span><Link to={`/user/${(i.data as ModAddCommunity).other_user_id}`}>{(i.data as ModAddCommunity).other_user_name}</Link></span> + <span> as a mod to the community </span> + <span><Link to={`/community/${i.data.community_id}`}>{i.data.community_name}</Link></span> + </> + } + </td> + </tr> + ) + } + + </tbody> + ); + + } + + render() { + return ( + <div class="container"> + {this.state.loading ? + <h4 class=""><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> : + <div> + <h4>Modlog</h4> + <div class="table-responsive"> + <table id="modlog_table" class="table table-sm table-hover"> + <thead class="pointer"> + <tr> + <th>Time</th> + <th>Mod</th> + <th>Action</th> + </tr> + </thead> + {this.combined()} + </table> + </div> + </div> + } + </div> + ); + } + + parseMessage(msg: any) { + console.log(msg); + let op: UserOperation = msgOp(msg); + if (msg.error) { + alert(msg.error); + return; + } else if (op == UserOperation.GetModlog) { + let res: GetModlogResponse = msg; + this.state.loading = false; + this.state.removed_posts = res.removed_posts; + this.state.locked_posts = res.locked_posts; + this.state.removed_comments = res.removed_comments; + this.state.removed_communities = res.removed_communities; + this.state.banned_from_community = res.banned_from_community; + this.state.added_to_community = res.added_to_community; + + this.setState(this.state); + } + } +} diff --git a/ui/src/components/moment-time.tsx b/ui/src/components/moment-time.tsx index b7402e7e..c8826695 100644 --- a/ui/src/components/moment-time.tsx +++ b/ui/src/components/moment-time.tsx @@ -3,7 +3,8 @@ import * as moment from 'moment'; interface MomentTimeProps { data: { - published: string; + published?: string; + when_?: string; updated?: string; } } @@ -20,8 +21,9 @@ export class MomentTime extends Component<MomentTimeProps, any> { <span title={this.props.data.updated} className="font-italics">modified {moment.utc(this.props.data.updated).fromNow()}</span> ) } else { + let str = this.props.data.published || this.props.data.when_; return ( - <span title={this.props.data.published}>{moment.utc(this.props.data.published).fromNow()}</span> + <span title={str}>{moment.utc(str).fromNow()}</span> ) } } diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index 5c51b699..64bf6e01 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -57,6 +57,9 @@ export class Navbar extends Component<any, NavbarState> { <Link class="nav-link" to="/communities">Forums</Link> </li> <li class="nav-item"> + <Link class="nav-link" to="/modlog">Modlog</Link> + </li> + <li class="nav-item"> <Link class="nav-link" to="/create_post">Create Post</Link> </li> <li class="nav-item"> diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx index 67a3f42e..b2042c1f 100644 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@ -2,7 +2,7 @@ import { Component, linkEvent } from 'inferno'; import { Subscription } from "rxjs"; import { retryWhen, delay, take } from 'rxjs/operators'; import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse, ListCommunitiesForm, SortType } from '../interfaces'; -import { WebSocketService } from '../services'; +import { WebSocketService, UserService } from '../services'; import { msgOp } from '../utils'; import * as autosize from 'autosize'; @@ -26,7 +26,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> { postForm: { name: null, auth: null, - community_id: null + community_id: null, + creator_id: UserService.Instance.loggedIn ? UserService.Instance.user.id : null }, communities: [], loading: false @@ -43,6 +44,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> { name: this.props.post.name, community_id: this.props.post.community_id, edit_id: this.props.post.id, + creator_id: this.props.post.creator_id, url: this.props.post.url, auth: null } diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx index d52dc937..d2bfc7bd 100644 --- a/ui/src/components/post-listing.tsx +++ b/ui/src/components/post-listing.tsx @@ -8,6 +8,8 @@ import { mdToHtml } from '../utils'; interface PostListingState { showEdit: boolean; + showRemoveDialog: boolean; + removeReason: string; iframeExpanded: boolean; } @@ -23,6 +25,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> { private emptyState: PostListingState = { showEdit: false, + showRemoveDialog: false, + removeReason: null, iframeExpanded: false } @@ -59,20 +63,34 @@ export class PostListing extends Component<PostListingProps, PostListingState> { <div className="ml-4"> {post.url ? <div className="mb-0"> - <h4 className="d-inline"><a className="text-white" href={post.url}>{post.name}</a></h4> - <small><a className="ml-2 text-muted font-italic" href={post.url}>{(new URL(post.url)).hostname}</a></small> - { !this.state.iframeExpanded - ? <span class="pointer ml-2 text-muted small" title="Expand here" onClick={linkEvent(this, this.handleIframeExpandClick)}>+</span> - : - <span> - <span class="pointer ml-2 text-muted small" onClick={linkEvent(this, this.handleIframeExpandClick)}>-</span> - <div class="embed-responsive embed-responsive-1by1"> - <iframe scrolling="yes" class="embed-responsive-item" src={post.url}></iframe> - </div> - </span> + <h4 className="d-inline"><a className="text-white" href={post.url}>{post.name}</a> + {post.removed && + <small className="ml-2 text-muted font-italic">removed</small> + } + {post.locked && + <small className="ml-2 text-muted font-italic">locked</small> } - </div> - : <h4 className="mb-0"><Link className="text-white" to={`/post/${post.id}`}>{post.name}</Link></h4> + </h4> + <small><a className="ml-2 text-muted font-italic" href={post.url}>{(new URL(post.url)).hostname}</a></small> + { !this.state.iframeExpanded + ? <span class="pointer ml-2 text-muted small" title="Expand here" onClick={linkEvent(this, this.handleIframeExpandClick)}>+</span> + : + <span> + <span class="pointer ml-2 text-muted small" onClick={linkEvent(this, this.handleIframeExpandClick)}>-</span> + <div class="embed-responsive embed-responsive-1by1"> + <iframe scrolling="yes" class="embed-responsive-item" src={post.url}></iframe> + </div> + </span> + } + </div> + : <h4 className="mb-0"><Link className="text-white" to={`/post/${post.id}`}>{post.name}</Link> + {post.removed && + <small className="ml-2 text-muted font-italic">removed</small> + } + {post.locked && + <small className="ml-2 text-muted font-italic">locked</small> + } + </h4> } </div> <div className="details ml-4 mb-1"> @@ -102,16 +120,39 @@ export class PostListing extends Component<PostListingProps, PostListingState> { <Link className="text-muted" to={`/post/${post.id}`}>{post.number_of_comments} Comments</Link> </li> </ul> - {this.myPost && + {this.props.editable && <ul class="list-inline mb-1 text-muted small font-weight-bold"> - <li className="list-inline-item"> - <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span> - </li> - <li className="list-inline-item"> - <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span> - </li> + {this.myPost && + <span> + <li className="list-inline-item"> + <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span> + </li> + <li className="list-inline-item mr-2"> + <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}>delete</span> + </li> + </span> + } + {this.props.post.am_mod && + <span> + <li className="list-inline-item"> + {!this.props.post.removed ? + <span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> : + <span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span> + } + |