summaryrefslogtreecommitdiffstats
path: root/ui/src
diff options
context:
space:
mode:
authorDessalines <tyhou13@gmx.com>2019-04-20 11:17:00 -0700
committerDessalines <tyhou13@gmx.com>2019-04-20 11:17:00 -0700
commit682413613e75618489d262bd033c3101da96abd7 (patch)
treef3bc26c1f2c1f75bdfcf7b6e5b22f24e93c7a757 /ui/src
parente14e6e53cd969039087df17bc4407d1b7444e05d (diff)
Mostly working, before merge
Diffstat (limited to 'ui/src')
-rw-r--r--ui/src/components/comment-node.tsx24
-rw-r--r--ui/src/components/comment-nodes.tsx2
-rw-r--r--ui/src/components/inbox.tsx177
-rw-r--r--ui/src/components/main.tsx18
-rw-r--r--ui/src/components/navbar.tsx16
-rw-r--r--ui/src/components/post.tsx1
-rw-r--r--ui/src/css/main.css7
-rw-r--r--ui/src/index.tsx2
-rw-r--r--ui/src/interfaces.ts16
-rw-r--r--ui/src/services/UserService.ts7
-rw-r--r--ui/src/services/WebSocketService.ts7
11 files changed, 264 insertions, 13 deletions
diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx
index 90cf5a54..cf7b1bce 100644
--- a/ui/src/components/comment-node.tsx
+++ b/ui/src/components/comment-node.tsx
@@ -25,6 +25,7 @@ interface CommentNodeProps {
noIndent?: boolean;
viewOnly?: boolean;
locked?: boolean;
+ markable?: boolean;
moderators: Array<CommunityUser>;
admins: Array<UserView>;
}
@@ -146,7 +147,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}
{!this.props.node.comment.banned &&
<li className="list-inline-item">
- <span class="pointer" onClick={linkEvent(this, this.addAdmin)}>{`${this.isAdmin ? 'remove' : 'appoint'} as admin`}</span>
+ <span class="pointer" onClick={linkEvent(this, this.handleAddAdmin)}>{`${this.isAdmin ? 'remove' : 'appoint'} as admin`}</span>
</li>
}
</>
@@ -156,6 +157,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<li className="list-inline-item">
<Link className="text-muted" to={`/post/${node.comment.post_id}/comment/${node.comment.id}`} target="_blank">link</Link>
</li>
+ {this.props.markable &&
+ <li className="list-inline-item">
+ <span class="pointer" onClick={linkEvent(this, this.handleMarkRead)}>{`mark as ${node.comment.read ? 'unread' : 'read'}`}</span>
+ </li>
+ }
</ul>
</div>
}
@@ -309,6 +315,20 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
i.setState(i.state);
}
+ handleMarkRead(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,
+ read: !i.props.node.comment.read,
+ auth: null
+ };
+ WebSocketService.Instance.editComment(form);
+ }
+
+
handleModBanFromCommunityShow(i: CommentNode) {
i.state.showBanDialog = true;
i.state.banType = BanType.Community;
@@ -382,7 +402,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
i.setState(i.state);
}
- addAdmin(i: CommentNode) {
+ handleAddAdmin(i: CommentNode) {
let form: AddAdminForm = {
user_id: i.props.node.comment.creator_id,
added: !i.isAdmin,
diff --git a/ui/src/components/comment-nodes.tsx b/ui/src/components/comment-nodes.tsx
index abbb1719..da67bbc7 100644
--- a/ui/src/components/comment-nodes.tsx
+++ b/ui/src/components/comment-nodes.tsx
@@ -12,6 +12,7 @@ interface CommentNodesProps {
noIndent?: boolean;
viewOnly?: boolean;
locked?: boolean;
+ markable?: boolean;
}
export class CommentNodes extends Component<CommentNodesProps, CommentNodesState> {
@@ -30,6 +31,7 @@ export class CommentNodes extends Component<CommentNodesProps, CommentNodesState
locked={this.props.locked}
moderators={this.props.moderators}
admins={this.props.admins}
+ markable={this.props.markable}
/>
)}
</div>
diff --git a/ui/src/components/inbox.tsx b/ui/src/components/inbox.tsx
new file mode 100644
index 00000000..e6ce6d13
--- /dev/null
+++ b/ui/src/components/inbox.tsx
@@ -0,0 +1,177 @@
+import { Component, linkEvent } from 'inferno';
+import { Link } from 'inferno-router';
+import { Subscription } from "rxjs";
+import { retryWhen, delay, take } from 'rxjs/operators';
+import { UserOperation, Comment, SortType, GetRepliesForm, GetRepliesResponse, CommentResponse } from '../interfaces';
+import { WebSocketService, UserService } from '../services';
+import { msgOp } from '../utils';
+import { CommentNodes } from './comment-nodes';
+
+enum UnreadType {
+ Unread, All
+}
+
+interface InboxState {
+ unreadType: UnreadType;
+ replies: Array<Comment>;
+ sort: SortType;
+ page: number;
+}
+
+export class Inbox extends Component<any, InboxState> {
+
+ private subscription: Subscription;
+ private emptyState: InboxState = {
+ unreadType: UnreadType.Unread,
+ replies: [],
+ sort: SortType.New,
+ page: 1,
+ }
+
+ 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')
+ );
+
+ this.refetch();
+ }
+
+ componentWillUnmount() {
+ this.subscription.unsubscribe();
+ }
+
+ render() {
+ let user = UserService.Instance.user;
+ return (
+ <div class="container">
+ <div class="row">
+ <div class="col-12">
+ <h5>Inbox for <Link to={`/user/${user.id}`}>{user.username}</Link></h5>
+ {this.selects()}
+ {this.replies()}
+ {this.paginator()}
+ </div>
+ </div>
+ </div>
+ )
+ }
+
+ selects() {
+ return (
+ <div className="mb-2">
+ <select value={this.state.unreadType} onChange={linkEvent(this, this.handleUnreadTypeChange)} class="custom-select w-auto">
+ <option disabled>Type</option>
+ <option value={UnreadType.Unread}>Unread</option>
+ <option value={UnreadType.All}>All</option>
+ </select>
+ <select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto ml-2">
+ <option disabled>Sort Type</option>
+ <option value={SortType.New}>New</option>
+ <option value={SortType.TopDay}>Top Day</option>
+ <option value={SortType.TopWeek}>Week</option>
+ <option value={SortType.TopMonth}>Month</option>
+ <option value={SortType.TopYear}>Year</option>
+ <option value={SortType.TopAll}>All</option>
+ </select>
+ </div>
+ )
+
+ }
+
+ replies() {
+ return (
+ <div>
+ {this.state.replies.map(reply =>
+ <CommentNodes nodes={[{comment: reply}]} noIndent viewOnly markable />
+ )}
+ </div>
+ );
+ }
+
+ paginator() {
+ return (
+ <div class="mt-2">
+ {this.state.page > 1 &&
+ <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button>
+ }
+ <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button>
+ </div>
+ );
+ }
+
+ nextPage(i: Inbox) {
+ i.state.page++;
+ i.setState(i.state);
+ i.refetch();
+ }
+
+ prevPage(i: Inbox) {
+ i.state.page--;
+ i.setState(i.state);
+ i.refetch();
+ }
+
+ handleUnreadTypeChange(i: Inbox, event: any) {
+ i.state.unreadType = Number(event.target.value);
+ i.state.page = 1;
+ i.setState(i.state);
+ i.refetch();
+ }
+
+ refetch() {
+ let form: GetRepliesForm = {
+ sort: SortType[this.state.sort],
+ unread_only: (this.state.unreadType == UnreadType.Unread),
+ page: this.state.page,
+ limit: 9999,
+ };
+ WebSocketService.Instance.getReplies(form);
+ }
+
+ handleSortChange(i: Inbox, event: any) {
+ i.state.sort = Number(event.target.value);
+ i.state.page = 1;
+ i.setState(i.state);
+ i.refetch();
+ }
+
+ parseMessage(msg: any) {
+ console.log(msg);
+ let op: UserOperation = msgOp(msg);
+ if (msg.error) {
+ alert(msg.error);
+ return;
+ } else if (op == UserOperation.GetReplies) {
+ let res: GetRepliesResponse = msg;
+ this.state.replies = res.replies;
+ this.sendRepliesCount();
+ this.setState(this.state);
+ } else if (op == UserOperation.EditComment) {
+ let res: CommentResponse = msg;
+
+ // If youre in the unread view, just remove it from the list
+ if (this.state.unreadType == UnreadType.Unread && res.comment.read) {
+ this.state.replies = this.state.replies.filter(r => r.id !== res.comment.id);
+ } else {
+ let found = this.state.replies.find(c => c.id == res.comment.id);
+ found.read = res.comment.read;
+ }
+
+ this.sendRepliesCount();
+ this.setState(this.state);
+ }
+ }
+
+ sendRepliesCount() {
+ UserService.Instance.sub.next({user: UserService.Instance.user, unreadCount: this.state.replies.filter(r => !r.read).length});
+ }
+}
+
diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx
index 01c70f94..e3d6f844 100644
--- a/ui/src/components/main.tsx
+++ b/ui/src/components/main.tsx
@@ -2,7 +2,7 @@ import { Component } from 'inferno';
import { Link } from 'inferno-router';
import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators';
-import { UserOperation, CommunityUser, GetFollowedCommunitiesResponse, ListCommunitiesForm, ListCommunitiesResponse, Community, SortType, GetSiteResponse } from '../interfaces';
+import { UserOperation, CommunityUser, GetFollowedCommunitiesResponse, ListCommunitiesForm, ListCommunitiesResponse, Community, SortType, GetSiteResponse, GetRepliesResponse, GetRepliesForm } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import { PostListings } from './post-listings';
import { msgOp, repoUrl, mdToHtml } from '../utils';
@@ -55,6 +55,15 @@ export class Main extends Component<any, State> {
if (UserService.Instance.user) {
WebSocketService.Instance.getFollowedCommunities();
+
+ // Get replies for the count
+ let repliesForm: GetRepliesForm = {
+ sort: SortType[SortType.New],
+ unread_only: true,
+ page: 1,
+ limit: 9999,
+ };
+ WebSocketService.Instance.getReplies(repliesForm);
}
let listCommunitiesForm: ListCommunitiesForm = {
@@ -176,7 +185,14 @@ export class Main extends Component<any, State> {
this.state.site.site = res.site;
this.state.site.banned = res.banned;
this.setState(this.state);
+ } else if (op == UserOperation.GetReplies) {
+ let res: GetRepliesResponse = msg;
+ this.sendRepliesCount(res);
}
}
+
+ sendRepliesCount(res: GetRepliesResponse) {
+ UserService.Instance.sub.next({user: UserService.Instance.user, unreadCount: res.replies.filter(r => !r.read).length});
+ }
}
diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx
index be98912e..fed49e6f 100644
--- a/ui/src/components/navbar.tsx
+++ b/ui/src/components/navbar.tsx
@@ -7,12 +7,14 @@ interface NavbarState {
isLoggedIn: boolean;
expanded: boolean;
expandUserDropdown: boolean;
+ unreadCount: number;
}
export class Navbar extends Component<any, NavbarState> {
emptyState: NavbarState = {
- isLoggedIn: UserService.Instance.user !== undefined,
+ isLoggedIn: (UserService.Instance.user !== undefined),
+ unreadCount: 0,
expanded: false,
expandUserDropdown: false
}
@@ -24,8 +26,9 @@ export class Navbar extends Component<any, NavbarState> {
// Subscribe to user changes
UserService.Instance.sub.subscribe(user => {
- let loggedIn: boolean = user !== undefined;
- this.setState({isLoggedIn: loggedIn});
+ this.state.isLoggedIn = user.user !== undefined;
+ this.state.unreadCount = user.unreadCount;
+ this.setState(this.state);
});
}
@@ -65,9 +68,13 @@ export class Navbar extends Component<any, NavbarState> {
<ul class="navbar-nav ml-auto mr-2">
{this.state.isLoggedIn ?
<>
+ {
<li className="nav-item">
- <Link class="nav-link" to="/communities">🖂</Link>
+ <Link class="nav-link" to="/inbox">🖂
+ {this.state.unreadCount> 0 && <span class="badge badge-light">{this.state.unreadCount}</span>}
+ </Link>
</li>
+ }
<li className={`nav-item dropdown ${this.state.expandUserDropdown && 'show'}`}>
<a class="pointer nav-link dropdown-toggle" onClick={linkEvent(this, this.expandUserDropdown)} role="button">
{UserService.Instance.user.username}
@@ -95,6 +102,7 @@ export class Navbar extends Component<any, NavbarState> {
handleLogoutClick(i: Navbar) {
i.state.expandUserDropdown = false;
UserService.Instance.logout();
+ i.context.router.history.push('/');
}
handleOverviewClick(i: Navbar) {
diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx
index 56b73f6e..3f243220 100644
--- a/ui/src/components/post.tsx
+++ b/ui/src/components/post.tsx
@@ -10,7 +10,6 @@ import { CommentForm } from './comment-form';
import { CommentNodes } from './comment-nodes';
import * as autosize from 'autosize';
-
interface PostState {
post: PostI;
comments: Array<Comment>;
diff --git a/ui/src/css/main.css b/ui/src/css/main.css
index 56fc2f46..3b74357d 100644
--- a/ui/src/css/main.css
+++ b/ui/src/css/main.css
@@ -82,3 +82,10 @@ blockquote {
margin: 0.5em 5px;
padding: 0.1em 5px;
}
+
+.badge-notify{
+ /* background:red; */
+ position:relative;
+ top: -20px;
+ left: -35px;
+}
diff --git a/ui/src/index.tsx b/ui/src/index.tsx
index d830bd3a..677d7678 100644
--- a/ui/src/index.tsx
+++ b/ui/src/index.tsx
@@ -13,6 +13,7 @@ import { Communities } from './components/communities';
import { User } from './components/user';
import { Modlog } from './components/modlog';
import { Setup } from './components/setup';
+import { Inbox } from './components/inbox';
import { Symbols } from './components/symbols';
import './css/bootstrap.min.css';
@@ -46,6 +47,7 @@ class Index extends Component<any, any> {
<Route path={`/community/:id`} component={Community} />
<Route path={`/user/:id/:heading`} component={User} />
<Route path={`/user/:id`} component={User} />
+ <Route path={`/inbox`} component={Inbox} />
<Route path={`/modlog/community/:community_id`} component={Modlog} />
<Route path={`/modlog`} component={Modlog} />
<Route path={`/setup`} component={Setup} />
diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts
index 8927a171..24bb6157 100644
--- a/ui/src/interfaces.ts
+++ b/ui/src/interfaces.ts
@@ -1,5 +1,5 @@
export enum UserOperation {
- Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser
+ Login, Register, CreateCommunity, CreatePost, ListCommunities, ListCategories, GetPost, GetCommunity, CreateComment, EditComment, SaveComment, CreateCommentLike, GetPosts, CreatePostLike, EditPost, SavePost, EditCommunity, FollowCommunity, GetFollowedCommunities, GetUserDetails, GetReplies, GetModlog, BanFromCommunity, AddModToCommunity, CreateSite, EditSite, GetSite, AddAdmin, BanUser
}
export enum CommentSortType {
@@ -153,6 +153,19 @@ export interface UserDetailsResponse {
posts: Array<Post>;
}
+export interface GetRepliesForm {
+ sort: string; // TODO figure this one out
+ page?: number;
+ limit?: number;
+ unread_only: boolean;
+ auth?: string;
+}
+
+export interface GetRepliesResponse {
+ op: string;
+ replies: Array<Comment>;
+}
+
export interface BanFromCommunityForm {
community_id: number;
user_id: number;
@@ -404,6 +417,7 @@ export interface CommentForm {
creator_id: number;
removed?: boolean;
reason?: string;
+ read?: boolean;
auth: string;
}
diff --git a/ui/src/services/UserService.ts b/ui/src/services/UserService.ts
index e182134d..d3259adb 100644
--- a/ui/src/services/UserService.ts
+++ b/ui/src/services/UserService.ts
@@ -4,9 +4,10 @@ import * as jwt_decode from 'jwt-decode';
import { Subject } from 'rxjs';
export class UserService {
+
private static _instance: UserService;
public user: User;
- public sub: Subject<User> = new Subject<User>();
+ public sub: Subject<{user: User, unreadCount: number}> = new Subject<{user: User, unreadCount: number}>();
private constructor() {
let jwt = Cookies.get("jwt");
@@ -28,7 +29,7 @@ export class UserService {
this.user = undefined;
Cookies.remove("jwt");
console.log("Logged out.");
- this.sub.next(undefined);
+ this.sub.next({user: undefined, unreadCount: 0});
}
public get auth(): string {
@@ -37,7 +38,7 @@ export class UserService {
private setUser(jwt: string) {
this.user = jwt_decode(jwt);
- this.sub.next(this.user);
+ this.sub.next({user: this.user, unreadCount: 0});
console.log(this.user);
}
diff --git a/ui/src/services/WebSocketService.ts b/ui/src/services/WebSocketService.ts
index b2c2a9e0..ac59631e 100644
--- a/ui/src/services/WebSocketService.ts
+++ b/ui/src/services/WebSocketService.ts
@@ -1,5 +1,5 @@
import { wsUri } from '../env';
-import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, SavePostForm, CommentForm, SaveCommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, AddAdminForm, BanUserForm, SiteForm, Site, UserView } from '../interfaces';
+import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, SavePostForm, CommentForm, SaveCommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm, GetModlogForm, BanFromCommunityForm, AddModToCommunityForm, AddAdminForm, BanUserForm, SiteForm, Site, UserView, GetRepliesForm } from '../interfaces';
import { webSocket } from 'rxjs/webSocket';
import { Subject } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
@@ -145,6 +145,11 @@ export class WebSocketService {
this.subject.next(this.wsSendWrapper(UserOperation.GetUserDetails, form));
}
+ public getReplies(form: GetRepliesForm) {
+ this.setAuth(form);
+ this.subject.next(this.wsSendWrapper(UserOperation.GetReplies, form));
+ }
+
public getModlog(form: GetModlogForm) {
this.subject.next(this.wsSendWrapper(UserOperation.GetModlog, form));
}