Merge branch 'dev'

pull/722/head
Dessalines 2019-04-29 10:18:38 -07:00
commit fa453a5955
10 changed files with 138 additions and 7 deletions

View File

@ -27,7 +27,7 @@ use actions::moderator::*;
#[derive(EnumString,ToString,Debug)] #[derive(EnumString,ToString,Debug)]
pub enum UserOperation { pub enum UserOperation {
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, Search 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, Search, MarkAllAsRead
} }
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
@ -478,6 +478,11 @@ pub struct SearchResponse {
posts: Vec<PostView>, posts: Vec<PostView>,
} }
#[derive(Serialize, Deserialize)]
pub struct MarkAllAsRead {
auth: String
}
/// `ChatServer` manages chat rooms and responsible for coordinating chat /// `ChatServer` manages chat rooms and responsible for coordinating chat
/// session. implementation is super primitive /// session. implementation is super primitive
pub struct ChatServer { pub struct ChatServer {
@ -728,6 +733,10 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
let search: Search = serde_json::from_str(data)?; let search: Search = serde_json::from_str(data)?;
search.perform(chat, msg.id) search.perform(chat, msg.id)
}, },
UserOperation::MarkAllAsRead => {
let mark_all_as_read: MarkAllAsRead = serde_json::from_str(data)?;
mark_all_as_read.perform(chat, msg.id)
},
} }
} }
@ -2709,3 +2718,56 @@ impl Perform for Search {
) )
} }
} }
impl Perform for MarkAllAsRead {
fn op_type(&self) -> UserOperation {
UserOperation::MarkAllAsRead
}
fn perform(&self, _chat: &mut ChatServer, _addr: usize) -> Result<String, Error> {
let conn = establish_connection();
let claims = match Claims::decode(&self.auth) {
Ok(claims) => claims.claims,
Err(_e) => {
return Err(self.error("Not logged in."))?
}
};
let user_id = claims.id;
let replies = ReplyView::get_replies(&conn, user_id, &SortType::New, true, Some(1), Some(999))?;
for reply in &replies {
let comment_form = CommentForm {
content: reply.to_owned().content,
parent_id: reply.to_owned().parent_id,
post_id: reply.to_owned().post_id,
creator_id: reply.to_owned().creator_id,
removed: None,
read: Some(true),
updated: reply.to_owned().updated
};
let _updated_comment = match Comment::update(&conn, reply.id, &comment_form) {
Ok(comment) => comment,
Err(_e) => {
return Err(self.error("Couldn't update Comment"))?
}
};
}
let replies = ReplyView::get_replies(&conn, user_id, &SortType::New, true, Some(1), Some(999))?;
Ok(
serde_json::to_string(
&GetRepliesResponse {
op: self.op_type().to_string(),
replies: replies,
}
)?
)
}
}

View File

@ -19,6 +19,7 @@
"@types/js-cookie": "^2.2.1", "@types/js-cookie": "^2.2.1",
"@types/jwt-decode": "^2.2.1", "@types/jwt-decode": "^2.2.1",
"@types/markdown-it": "^0.0.7", "@types/markdown-it": "^0.0.7",
"@types/markdown-it-container": "^2.0.2",
"autosize": "^4.0.2", "autosize": "^4.0.2",
"classcat": "^1.1.3", "classcat": "^1.1.3",
"dotenv": "^6.1.0", "dotenv": "^6.1.0",
@ -27,6 +28,7 @@
"js-cookie": "^2.2.0", "js-cookie": "^2.2.0",
"jwt-decode": "^2.2.0", "jwt-decode": "^2.2.0",
"markdown-it": "^8.4.2", "markdown-it": "^8.4.2",
"markdown-it-container": "^2.0.0",
"moment": "^2.24.0", "moment": "^2.24.0",
"rxjs": "^6.4.0" "rxjs": "^6.4.0"
}, },

View File

@ -18,13 +18,23 @@ export class CreatePost extends Component<any, any> {
<div class="row"> <div class="row">
<div class="col-12 col-lg-6 mb-4"> <div class="col-12 col-lg-6 mb-4">
<h5>Create a Post</h5> <h5>Create a Post</h5>
<PostForm onCreate={this.handlePostCreate}/> <PostForm onCreate={this.handlePostCreate} prevCommunityName={this.prevCommunityName} />
</div> </div>
</div> </div>
</div> </div>
) )
} }
get prevCommunityName(): string {
if (this.props.location.state) {
let lastLocation = this.props.location.state.prevPath;
if (lastLocation.includes("/c/")) {
return lastLocation.split("/c/")[1];
}
}
return undefined;
}
handlePostCreate(id: number) { handlePostCreate(id: number) {
this.props.history.push(`/post/${id}`); this.props.history.push(`/post/${id}`);
} }

View File

@ -58,7 +58,16 @@ export class Inbox extends Component<any, InboxState> {
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<h5>Inbox for <Link to={`/u/${user.username}`}>{user.username}</Link></h5> <h5 class="mb-0">
<span>Inbox for <Link to={`/u/${user.username}`}>{user.username}</Link></span>
</h5>
{this.state.replies.length > 0 && this.state.unreadType == UnreadType.Unread &&
<ul class="list-inline mb-1 text-muted small font-weight-bold">
<li className="list-inline-item">
<span class="pointer" onClick={this.markAllAsRead}>mark all as read</span>
</li>
</ul>
}
{this.selects()} {this.selects()}
{this.replies()} {this.replies()}
{this.paginator()} {this.paginator()}
@ -147,13 +156,17 @@ export class Inbox extends Component<any, InboxState> {
i.refetch(); i.refetch();
} }
markAllAsRead() {
WebSocketService.Instance.markAllAsRead();
}
parseMessage(msg: any) { parseMessage(msg: any) {
console.log(msg); console.log(msg);
let op: UserOperation = msgOp(msg); let op: UserOperation = msgOp(msg);
if (msg.error) { if (msg.error) {
alert(msg.error); alert(msg.error);
return; return;
} else if (op == UserOperation.GetReplies) { } else if (op == UserOperation.GetReplies || op == UserOperation.MarkAllAsRead) {
let res: GetRepliesResponse = msg; let res: GetRepliesResponse = msg;
this.state.replies = res.replies; this.state.replies = res.replies;
this.sendRepliesCount(); this.sendRepliesCount();

View File

@ -79,7 +79,7 @@ export class Navbar extends Component<any, NavbarState> {
<Link class="nav-link" to="/search">Search</Link> <Link class="nav-link" to="/search">Search</Link>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<Link class="nav-link" to="/create_post">Create Post</Link> <Link class="nav-link" to={{pathname: '/create_post', state: { prevPath: this.currentLocation }}}>Create Post</Link>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<Link class="nav-link" to="/create_community">Create Community</Link> <Link class="nav-link" to="/create_community">Create Community</Link>
@ -165,6 +165,10 @@ export class Navbar extends Component<any, NavbarState> {
} }
} }
get currentLocation() {
return this.context.router.history.location.pathname;
}
sendRepliesCount(res: GetRepliesResponse) { sendRepliesCount(res: GetRepliesResponse) {
UserService.Instance.sub.next({user: UserService.Instance.user, unreadCount: res.replies.filter(r => !r.read).length}); UserService.Instance.sub.next({user: UserService.Instance.user, unreadCount: res.replies.filter(r => !r.read).length});
} }

View File

@ -8,6 +8,7 @@ import * as autosize from 'autosize';
interface PostFormProps { interface PostFormProps {
post?: Post; // If a post is given, that means this is an edit post?: Post; // If a post is given, that means this is an edit
prevCommunityName?: string;
onCancel?(): any; onCancel?(): any;
onCreate?(id: number): any; onCreate?(id: number): any;
onEdit?(post: Post): any; onEdit?(post: Post): any;
@ -170,6 +171,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
this.state.communities = res.communities; this.state.communities = res.communities;
if (this.props.post) { if (this.props.post) {
this.state.postForm.community_id = this.props.post.community_id; this.state.postForm.community_id = this.props.post.community_id;
} else if (this.props.prevCommunityName) {
let foundCommunityId = res.communities.find(r => r.name == this.props.prevCommunityName).id;
this.state.postForm.community_id = foundCommunityId;
} else { } else {
this.state.postForm.community_id = res.communities[0].id; this.state.postForm.community_id = res.communities[0].id;
} }

View File

@ -1,5 +1,5 @@
export enum UserOperation { export enum UserOperation {
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, Search 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, Search, MarkAllAsRead
} }
export enum CommentSortType { export enum CommentSortType {

View File

@ -177,6 +177,12 @@ export class WebSocketService {
this.subject.next(this.wsSendWrapper(UserOperation.Search, form)); this.subject.next(this.wsSendWrapper(UserOperation.Search, form));
} }
public markAllAsRead() {
let form = {};
this.setAuth(form);
this.subject.next(this.wsSendWrapper(UserOperation.MarkAllAsRead, form));
}
private wsSendWrapper(op: UserOperation, data: any) { private wsSendWrapper(op: UserOperation, data: any) {
let send = { op: UserOperation[op], data: data }; let send = { op: UserOperation[op], data: data };
console.log(send); console.log(send);

View File

@ -1,5 +1,6 @@
import { UserOperation, Comment, User, SortType, ListingType } from './interfaces'; import { UserOperation, Comment, User, SortType, ListingType } from './interfaces';
import * as markdown_it from 'markdown-it'; import * as markdown_it from 'markdown-it';
import * as markdown_it_container from 'markdown-it-container';
export let repoUrl = 'https://github.com/dessalines/lemmy'; export let repoUrl = 'https://github.com/dessalines/lemmy';
@ -12,6 +13,23 @@ var md = new markdown_it({
html: true, html: true,
linkify: true, linkify: true,
typographer: true typographer: true
}).use(markdown_it_container, 'spoiler', {
validate: function(params: any) {
return params.trim().match(/^spoiler\s+(.*)$/);
},
render: function (tokens: any, idx: any) {
var m = tokens[idx].info.trim().match(/^spoiler\s+(.*)$/);
if (tokens[idx].nesting === 1) {
// opening tag
return '<details><summary>' + md.utils.escapeHtml(m[1]) + '</summary>\n';
} else {
// closing tag
return '</details>\n';
}
}
}); });
export function hotRank(comment: Comment): number { export function hotRank(comment: Comment): number {

View File

@ -38,7 +38,14 @@
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-2.1.0.tgz#ea3dd64c4805597311790b61e872cbd1ed2cd806" resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-2.1.0.tgz#ea3dd64c4805597311790b61e872cbd1ed2cd806"
integrity sha512-Q7DYAOi9O/+cLLhdaSvKdaumWyHbm7HAk/bFwwyTuU0arR5yyCeW5GOoqt4tJTpDRxhpx9Q8kQL6vMpuw9hDSw== integrity sha512-Q7DYAOi9O/+cLLhdaSvKdaumWyHbm7HAk/bFwwyTuU0arR5yyCeW5GOoqt4tJTpDRxhpx9Q8kQL6vMpuw9hDSw==
"@types/markdown-it@^0.0.7": "@types/markdown-it-container@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/markdown-it-container/-/markdown-it-container-2.0.2.tgz#0e624653415a1c2f088a5ae51f7bfff480c03f49"
integrity sha512-T770GL+zJz8Ssh1NpLiOruYhrU96yb8ovPSegLrWY5XIkJc6PVVC7kH/oQaVD0rkePpWMFJK018OgS/pwviOMw==
dependencies:
"@types/markdown-it" "*"
"@types/markdown-it@*", "@types/markdown-it@^0.0.7":
version "0.0.7" version "0.0.7"
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.7.tgz#75070485a3d8ad11e7deb8287f4430be15bf4d39" resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.7.tgz#75070485a3d8ad11e7deb8287f4430be15bf4d39"
integrity sha512-WyL6pa76ollQFQNEaLVa41ZUUvDvPY+qAUmlsphnrpL6I9p1m868b26FyeoOmo7X3/Ta/S9WKXcEYXUSHnxoVQ== integrity sha512-WyL6pa76ollQFQNEaLVa41ZUUvDvPY+qAUmlsphnrpL6I9p1m868b26FyeoOmo7X3/Ta/S9WKXcEYXUSHnxoVQ==
@ -1674,6 +1681,11 @@ map-visit@^1.0.0:
dependencies: dependencies:
object-visit "^1.0.0" object-visit "^1.0.0"
markdown-it-container@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/markdown-it-container/-/markdown-it-container-2.0.0.tgz#0019b43fd02eefece2f1960a2895fba81a404695"
integrity sha1-ABm0P9Au7+zi8ZYKKJX7qBpARpU=
markdown-it@^8.4.2: markdown-it@^8.4.2:
version "8.4.2" version "8.4.2"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54" resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54"