summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorDessalines <tyhou13@gmx.com>2019-04-15 16:12:06 -0700
committerDessalines <tyhou13@gmx.com>2019-04-15 16:12:06 -0700
commite94885eb97b3240ed9cec7f97d0f405b2819e922 (patch)
treeb407f0b6ed9be27682e3767271e34933c947cb2a /ui
parent8590a612f633fe6ba8f8b18379a8a822a3b3019b (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.tsx13
-rw-r--r--ui/src/components/comment-node.tsx169
-rw-r--r--ui/src/components/comment-nodes.tsx11
-rw-r--r--ui/src/components/communities.tsx3
-rw-r--r--ui/src/components/community.tsx6
-rw-r--r--ui/src/components/modlog.tsx184
-rw-r--r--ui/src/components/moment-time.tsx6
-rw-r--r--ui/src/components/navbar.tsx3
-rw-r--r--ui/src/components/post-form.tsx6
-rw-r--r--ui/src/components/post-listing.tsx122
-rw-r--r--ui/src/components/post.tsx22
-rw-r--r--ui/src/components/sidebar.tsx151
-rw-r--r--ui/src/components/user.tsx19
-rw-r--r--ui/src/index.tsx2
-rw-r--r--ui/src/interfaces.ts371
-rw-r--r--ui/src/main.css5
-rw-r--r--ui/src/services/WebSocketService.ts16
-rw-r--r--ui/src/utils.ts8
-rw-r--r--ui/src/version.ts2
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>
+ }
+