From: Dessalines Date: Fri, 24 Jan 2020 00:17:42 +0000 (-0500) Subject: Done merging http-api and private_message X-Git-Url: http://these/git/%7B%60%24%7BwebArchiveUrl%7D/%22%7B%7D/%22https:/nerdica.net/static/%24%7Bargs.thread.url%7D?a=commitdiff_plain;h=ac1d5f2b86b0816cc9b5794dc6c38a38365ed839;p=lemmy.git Done merging http-api and private_message --- ac1d5f2b86b0816cc9b5794dc6c38a38365ed839 diff --cc server/src/api/user.rs index 046da6fb,e1ddb1ca..8d2db104 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@@ -168,42 -158,6 +159,40 @@@ pub struct PasswordChange password_verify: String, } +#[derive(Serialize, Deserialize)] +pub struct CreatePrivateMessage { + content: String, + recipient_id: i32, + auth: String, +} + +#[derive(Serialize, Deserialize)] +pub struct EditPrivateMessage { + edit_id: i32, + content: Option, + deleted: Option, + read: Option, + auth: String, +} + +#[derive(Serialize, Deserialize)] +pub struct GetPrivateMessages { + unread_only: bool, + page: Option, + limit: Option, + auth: String, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct PrivateMessagesResponse { - op: String, + messages: Vec, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct PrivateMessageResponse { - op: String, + message: PrivateMessageView, +} + impl Perform for Oper { fn perform(&self, conn: &PgConnection) -> Result { let data: &Login = &self.data; @@@ -805,34 -732,7 +773,31 @@@ impl Perform for Op }; } + // messages + let messages = PrivateMessageQueryBuilder::create(&conn, user_id) + .page(1) + .limit(999) + .unread_only(true) + .list()?; + + for message in &messages { + let private_message_form = PrivateMessageForm { + content: None, + creator_id: message.to_owned().creator_id, + recipient_id: message.to_owned().recipient_id, + deleted: None, + read: Some(true), + updated: None, + }; + + let _updated_message = match PrivateMessage::update(&conn, message.id, &private_message_form) + { + Ok(message) => message, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_private_message").into()), ++ Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()), + }; + } + - Ok(GetRepliesResponse { - op: self.op.to_string(), - replies: vec![], - }) + Ok(GetRepliesResponse { replies: vec![] }) } } @@@ -972,150 -868,3 +933,141 @@@ impl Perform for Oper for Oper { + fn perform(&self, conn: &PgConnection) -> Result { + let data: &CreatePrivateMessage = &self.data; + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), ++ Err(_e) => return Err(APIError::err("not_logged_in").into()), + }; + + let user_id = claims.id; + + let hostname = &format!("https://{}", Settings::get().hostname); + + // Check for a site ban + if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err(&self.op, "site_ban").into()); ++ return Err(APIError::err("site_ban").into()); + } + + let content_slurs_removed = remove_slurs(&data.content.to_owned()); + + let private_message_form = PrivateMessageForm { + content: Some(content_slurs_removed.to_owned()), + creator_id: user_id, + recipient_id: data.recipient_id, + deleted: None, + read: None, + updated: None, + }; + + let inserted_private_message = match PrivateMessage::create(&conn, &private_message_form) { + Ok(private_message) => private_message, + Err(_e) => { - return Err(APIError::err(&self.op, "couldnt_create_private_message").into()); ++ return Err(APIError::err("couldnt_create_private_message").into()); + } + }; + + // Send notifications to the recipient + let recipient_user = User_::read(&conn, data.recipient_id)?; + if recipient_user.send_notifications_to_email { + if let Some(email) = recipient_user.email { + let subject = &format!( + "{} - Private Message from {}", + Settings::get().hostname, + claims.username + ); + let html = &format!( + "

Private Message


{} - {}

inbox", + claims.username, &content_slurs_removed, hostname + ); + match send_email(subject, &email, &recipient_user.name, html) { + Ok(_o) => _o, + Err(e) => eprintln!("{}", e), + }; + } + } + - let private_message_view = PrivateMessageView::read(&conn, inserted_private_message.id)?; ++ let message = PrivateMessageView::read(&conn, inserted_private_message.id)?; + - Ok(PrivateMessageResponse { - op: self.op.to_string(), - message: private_message_view, - }) ++ Ok(PrivateMessageResponse { message }) + } +} + +impl Perform for Oper { + fn perform(&self, conn: &PgConnection) -> Result { + let data: &EditPrivateMessage = &self.data; + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), ++ Err(_e) => return Err(APIError::err("not_logged_in").into()), + }; + + let user_id = claims.id; + + let orig_private_message = PrivateMessage::read(&conn, data.edit_id)?; + + // Check for a site ban + if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err(&self.op, "site_ban").into()); ++ return Err(APIError::err("site_ban").into()); + } + + // Check to make sure they are the creator (or the recipient marking as read + if !(data.read.is_some() && orig_private_message.recipient_id.eq(&user_id) + || orig_private_message.creator_id.eq(&user_id)) + { - return Err(APIError::err(&self.op, "no_private_message_edit_allowed").into()); ++ return Err(APIError::err("no_private_message_edit_allowed").into()); + } + + let content_slurs_removed = match &data.content { + Some(content) => Some(remove_slurs(content)), + None => None, + }; + + let private_message_form = PrivateMessageForm { + content: content_slurs_removed, + creator_id: orig_private_message.creator_id, + recipient_id: orig_private_message.recipient_id, + deleted: data.deleted.to_owned(), + read: data.read.to_owned(), + updated: if data.read.is_some() { + orig_private_message.updated + } else { + Some(naive_now()) + }, + }; + + let _updated_private_message = + match PrivateMessage::update(&conn, data.edit_id, &private_message_form) { + Ok(private_message) => private_message, - Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_private_message").into()), ++ Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()), + }; + - let private_message_view = PrivateMessageView::read(&conn, data.edit_id)?; ++ let message = PrivateMessageView::read(&conn, data.edit_id)?; + - Ok(PrivateMessageResponse { - op: self.op.to_string(), - message: private_message_view, - }) ++ Ok(PrivateMessageResponse { message }) + } +} + +impl Perform for Oper { + fn perform(&self, conn: &PgConnection) -> Result { + let data: &GetPrivateMessages = &self.data; + + let claims = match Claims::decode(&data.auth) { + Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err(&self.op, "not_logged_in").into()), ++ Err(_e) => return Err(APIError::err("not_logged_in").into()), + }; + + let user_id = claims.id; + + let messages = PrivateMessageQueryBuilder::create(&conn, user_id) + .page(data.page) + .limit(data.limit) + .unread_only(data.unread_only) + .list()?; + - Ok(PrivateMessagesResponse { - op: self.op.to_string(), - messages, - }) ++ Ok(PrivateMessagesResponse { messages }) + } +} diff --cc server/src/websocket/mod.rs index 74f47ad3,1be3a8e0..021bcb41 --- a/server/src/websocket/mod.rs +++ b/server/src/websocket/mod.rs @@@ -1,1 -1,44 +1,47 @@@ pub mod server; + + #[derive(EnumString, ToString, Debug)] + pub enum UserOperation { + Login, + Register, + CreateCommunity, + CreatePost, + ListCommunities, + ListCategories, + GetPost, + GetCommunity, + CreateComment, + EditComment, + SaveComment, + CreateCommentLike, + GetPosts, + CreatePostLike, + EditPost, + SavePost, + EditCommunity, + FollowCommunity, + GetFollowedCommunities, + GetUserDetails, + GetReplies, + GetUserMentions, + EditUserMention, + GetModlog, + BanFromCommunity, + AddModToCommunity, + CreateSite, + EditSite, + GetSite, + AddAdmin, + BanUser, + Search, + MarkAllAsRead, + SaveUserSettings, + TransferCommunity, + TransferSite, + DeleteAccount, + PasswordReset, + PasswordChange, ++ CreatePrivateMessage, ++ EditPrivateMessage, ++ GetPrivateMessages, + } diff --cc server/src/websocket/server.rs index 5efcb7bf,3015a80d..b1d4f138 --- a/server/src/websocket/server.rs +++ b/server/src/websocket/server.rs @@@ -513,55 -495,27 +495,37 @@@ fn parse_json_message(chat: &mut ChatSe UserOperation::GetSite => { let online: usize = chat.sessions.len(); let get_site: GetSite = serde_json::from_str(data)?; - let mut res = Oper::new(user_operation, get_site).perform(&conn)?; + let mut res = Oper::new(get_site).perform(&conn)?; res.online = online; - Ok(serde_json::to_string(&res)?) + to_json_string(&user_operation, &res) } UserOperation::Search => { - let search: Search = serde_json::from_str(data)?; - let res = Oper::new(user_operation, search).perform(&conn)?; - Ok(serde_json::to_string(&res)?) + do_user_operation::(user_operation, data, &conn) } UserOperation::TransferCommunity => { - let transfer_community: TransferCommunity = serde_json::from_str(data)?; - let res = Oper::new(user_operation, transfer_community).perform(&conn)?; - Ok(serde_json::to_string(&res)?) + do_user_operation::(user_operation, data, &conn) } UserOperation::TransferSite => { - let transfer_site: TransferSite = serde_json::from_str(data)?; - let res = Oper::new(user_operation, transfer_site).perform(&conn)?; - Ok(serde_json::to_string(&res)?) + do_user_operation::(user_operation, data, &conn) } UserOperation::DeleteAccount => { - let delete_account: DeleteAccount = serde_json::from_str(data)?; - let res = Oper::new(user_operation, delete_account).perform(&conn)?; - Ok(serde_json::to_string(&res)?) + do_user_operation::(user_operation, data, &conn) } UserOperation::PasswordReset => { - let password_reset: PasswordReset = serde_json::from_str(data)?; - let res = Oper::new(user_operation, password_reset).perform(&conn)?; - Ok(serde_json::to_string(&res)?) + do_user_operation::(user_operation, data, &conn) } UserOperation::PasswordChange => { - let password_change: PasswordChange = serde_json::from_str(data)?; - let res = Oper::new(user_operation, password_change).perform(&conn)?; - Ok(serde_json::to_string(&res)?) + do_user_operation::(user_operation, data, &conn) } + UserOperation::CreatePrivateMessage => { + chat.check_rate_limit_message(msg.id)?; - let create_private_message: CreatePrivateMessage = serde_json::from_str(data)?; - let res = Oper::new(user_operation, create_private_message).perform(&conn)?; - Ok(serde_json::to_string(&res)?) ++ do_user_operation::(user_operation, data, &conn) + } + UserOperation::EditPrivateMessage => { - let edit_private_message: EditPrivateMessage = serde_json::from_str(data)?; - let res = Oper::new(user_operation, edit_private_message).perform(&conn)?; - Ok(serde_json::to_string(&res)?) ++ do_user_operation::(user_operation, data, &conn) + } + UserOperation::GetPrivateMessages => { - let messages: GetPrivateMessages = serde_json::from_str(data)?; - let res = Oper::new(user_operation, messages).perform(&conn)?; - Ok(serde_json::to_string(&res)?) ++ do_user_operation::(user_operation, data, &conn) + } } } diff --cc ui/src/components/communities.tsx index 129051fb,ebcbc345..867cfd81 --- a/ui/src/components/communities.tsx +++ b/ui/src/components/communities.tsx @@@ -10,9 -10,10 +10,10 @@@ import FollowCommunityForm, ListCommunitiesForm, SortType, + WebSocketJsonResponse, } from '../interfaces'; import { WebSocketService } from '../services'; - import { msgOp, toast } from '../utils'; -import { wsJsonToRes } from '../utils'; ++import { wsJsonToRes, toast } from '../utils'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@@ -231,15 -228,15 +232,15 @@@ export class Communities extends Compon WebSocketService.Instance.listCommunities(listCommunitiesForm); } - parseMessage(msg: any) { + parseMessage(msg: WebSocketJsonResponse) { console.log(msg); - let op: UserOperation = msgOp(msg); - if (msg.error) { + let res = wsJsonToRes(msg); + if (res.error) { - alert(i18n.t(res.error)); + toast(i18n.t(msg.error), 'danger'); return; - } else if (op == UserOperation.ListCommunities) { - let res: ListCommunitiesResponse = msg; - this.state.communities = res.communities; + } else if (res.op == UserOperation.ListCommunities) { + let data = res.data as ListCommunitiesResponse; + this.state.communities = data.communities; this.state.communities.sort( (a, b) => b.number_of_subscribers - a.number_of_subscribers ); diff --cc ui/src/components/community-form.tsx index ec58b010,14cd8e4f..4dc7bfcb --- a/ui/src/components/community-form.tsx +++ b/ui/src/components/community-form.tsx @@@ -8,9 -8,9 +8,10 @@@ import ListCategoriesResponse, CommunityResponse, GetSiteResponse, ++ WebSocketJsonResponse, } from '../interfaces'; import { WebSocketService } from '../services'; - import { msgOp, capitalizeFirstLetter, toast } from '../utils'; -import { wsJsonToRes, capitalizeFirstLetter } from '../utils'; ++import { wsJsonToRes, capitalizeFirstLetter, toast } from '../utils'; import autosize from 'autosize'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@@ -239,11 -239,11 +240,11 @@@ export class CommunityForm extends Comp i.props.onCancel(); } - parseMessage(msg: any) { - let op: UserOperation = msgOp(msg); + parseMessage(msg: WebSocketJsonResponse) { + let res = wsJsonToRes(msg); console.log(msg); - if (msg.error) { + if (res.error) { - alert(i18n.t(res.error)); + toast(i18n.t(msg.error), 'danger'); this.state.loading = false; this.setState(this.state); return; diff --cc ui/src/components/community.tsx index dfd4d6b3,357fe260..9d02dd86 --- a/ui/src/components/community.tsx +++ b/ui/src/components/community.tsx @@@ -254,43 -252,49 +255,43 @@@ export class Community extends Componen WebSocketService.Instance.getPosts(getPostsForm); } - parseMessage(msg: any) { + parseMessage(msg: WebSocketJsonResponse) { console.log(msg); - let op: UserOperation = msgOp(msg); - if (msg.error) { + let res = wsJsonToRes(msg); + if (res.error) { - alert(i18n.t(res.error)); + toast(i18n.t(msg.error), 'danger'); this.context.router.history.push('/'); return; - } else if (op == UserOperation.GetCommunity) { - let res: GetCommunityResponse = msg; - this.state.community = res.community; - this.state.moderators = res.moderators; - this.state.admins = res.admins; + } else if (res.op == UserOperation.GetCommunity) { + let data = res.data as GetCommunityResponse; + this.state.community = data.community; + this.state.moderators = data.moderators; + this.state.admins = data.admins; document.title = `/c/${this.state.community.name} - ${WebSocketService.Instance.site.name}`; this.setState(this.state); this.keepFetchingPosts(); - } else if (op == UserOperation.EditCommunity) { - let res: CommunityResponse = msg; - this.state.community = res.community; + } else if (res.op == UserOperation.EditCommunity) { + let data = res.data as CommunityResponse; + this.state.community = data.community; this.setState(this.state); - } else if (op == UserOperation.FollowCommunity) { - let res: CommunityResponse = msg; - this.state.community.subscribed = res.community.subscribed; + } else if (res.op == UserOperation.FollowCommunity) { + let data = res.data as CommunityResponse; + this.state.community.subscribed = data.community.subscribed; this.state.community.number_of_subscribers = - res.community.number_of_subscribers; + data.community.number_of_subscribers; this.setState(this.state); - } else if (op == UserOperation.GetPosts) { - let res: GetPostsResponse = msg; - this.state.posts = res.posts; + } else if (res.op == UserOperation.GetPosts) { + let data = res.data as GetPostsResponse; - - // TODO rework this - // This is needed to refresh the view - this.state.posts = undefined; - this.setState(this.state); - + this.state.posts = data.posts; this.state.loading = false; this.setState(this.state); - } else if (op == UserOperation.CreatePostLike) { - let res: CreatePostLikeResponse = msg; - let found = this.state.posts.find(c => c.id == res.post.id); - found.my_vote = res.post.my_vote; - found.score = res.post.score; - found.upvotes = res.post.upvotes; - found.downvotes = res.post.downvotes; + } else if (res.op == UserOperation.CreatePostLike) { + let data = res.data as CreatePostLikeResponse; + let found = this.state.posts.find(c => c.id == data.post.id); + found.my_vote = data.post.my_vote; + found.score = data.post.score; + found.upvotes = data.post.upvotes; + found.downvotes = data.post.downvotes; this.setState(this.state); } } diff --cc ui/src/components/inbox.tsx index bf090179,4aa9cebe..5c3ff6d2 --- a/ui/src/components/inbox.tsx +++ b/ui/src/components/inbox.tsx @@@ -12,15 -12,11 +12,16 @@@ import GetUserMentionsResponse, UserMentionResponse, CommentResponse, + WebSocketJsonResponse, + PrivateMessage as PrivateMessageI, + GetPrivateMessagesForm, + PrivateMessagesResponse, + PrivateMessageResponse, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; - import { msgOp, fetchLimit, isCommentType, toast } from '../utils'; -import { wsJsonToRes, fetchLimit } from '../utils'; ++import { wsJsonToRes, fetchLimit, isCommentType, toast } from '../utils'; import { CommentNodes } from './comment-nodes'; +import { PrivateMessage } from './private-message'; import { SortSelect } from './sort-select'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@@ -320,15 -297,15 +321,15 @@@ export class Inbox extends Component m.id === res.message.id ++ m => m.id === data.message.id + ); - found.content = res.message.content; - found.updated = res.message.updated; - found.deleted = res.message.deleted; ++ found.content = data.message.content; ++ found.updated = data.message.updated; ++ found.deleted = data.message.deleted; + // If youre in the unread view, just remove it from the list - if (this.state.unreadOrAll == UnreadOrAll.Unread && res.message.read) { ++ if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) { + this.state.messages = this.state.messages.filter( - r => r.id !== res.message.id ++ r => r.id !== data.message.id + ); + } else { - let found = this.state.messages.find(c => c.id == res.message.id); - found.read = res.message.read; ++ let found = this.state.messages.find(c => c.id == data.message.id); ++ found.read = data.message.read; + } + this.sendUnreadCount(); + window.scrollTo(0, 0); + this.setState(this.state); - } else if (op == UserOperation.MarkAllAsRead) { + } else if (res.op == UserOperation.MarkAllAsRead) { this.state.replies = []; this.state.mentions = []; + this.state.messages = []; + this.sendUnreadCount(); window.scrollTo(0, 0); this.setState(this.state); - } else if (op == UserOperation.EditComment) { - let res: CommentResponse = msg; - - let found = this.state.replies.find(c => c.id == res.comment.id); - found.content = res.comment.content; - found.updated = res.comment.updated; - found.removed = res.comment.removed; - found.deleted = res.comment.deleted; - found.upvotes = res.comment.upvotes; - found.downvotes = res.comment.downvotes; - found.score = res.comment.score; + } else if (res.op == UserOperation.EditComment) { + let data = res.data as CommentResponse; + + let found = this.state.replies.find(c => c.id == data.comment.id); + found.content = data.comment.content; + found.updated = data.comment.updated; + found.removed = data.comment.removed; + found.deleted = data.comment.deleted; + found.upvotes = data.comment.upvotes; + found.downvotes = data.comment.downvotes; + found.score = data.comment.score; // If youre in the unread view, just remove it from the list - if (this.state.unreadOrAll == UnreadOrAll.Unread && res.comment.read) { + if (this.state.unreadOrAll == UnreadOrAll.Unread && data.comment.read) { this.state.replies = this.state.replies.filter( - r => r.id !== res.comment.id + r => r.id !== data.comment.id ); } else { - let found = this.state.replies.find(c => c.id == res.comment.id); - found.read = res.comment.read; + let found = this.state.replies.find(c => c.id == data.comment.id); + found.read = data.comment.read; } this.sendUnreadCount(); this.setState(this.state); @@@ -417,25 -366,25 +418,25 @@@ } this.sendUnreadCount(); this.setState(this.state); - } else if (op == UserOperation.CreateComment) { + } else if (res.op == UserOperation.CreateComment) { // let res: CommentResponse = msg; - alert(i18n.t('reply_sent')); + toast(i18n.t('reply_sent')); // this.state.replies.unshift(res.comment); // TODO do this right // this.setState(this.state); - } else if (op == UserOperation.SaveComment) { - let res: CommentResponse = msg; - let found = this.state.replies.find(c => c.id == res.comment.id); - found.saved = res.comment.saved; + } else if (res.op == UserOperation.SaveComment) { + let data = res.data as CommentResponse; + let found = this.state.replies.find(c => c.id == data.comment.id); + found.saved = data.comment.saved; this.setState(this.state); - } else if (op == UserOperation.CreateCommentLike) { - let res: CommentResponse = msg; + } else if (res.op == UserOperation.CreateCommentLike) { + let data = res.data as CommentResponse; let found: Comment = this.state.replies.find( - c => c.id === res.comment.id + c => c.id === data.comment.id ); - found.score = res.comment.score; - found.upvotes = res.comment.upvotes; - found.downvotes = res.comment.downvotes; - if (res.comment.my_vote !== null) found.my_vote = res.comment.my_vote; + found.score = data.comment.score; + found.upvotes = data.comment.upvotes; + found.downvotes = data.comment.downvotes; + if (data.comment.my_vote !== null) found.my_vote = data.comment.my_vote; this.setState(this.state); } } diff --cc ui/src/components/login.tsx index 0c8350aa,29482f45..ac60ba74 --- a/ui/src/components/login.tsx +++ b/ui/src/components/login.tsx @@@ -8,9 -8,10 +8,10 @@@ import UserOperation, PasswordResetForm, GetSiteResponse, + WebSocketJsonResponse, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; - import { msgOp, validEmail, toast } from '../utils'; -import { wsJsonToRes, validEmail } from '../utils'; ++import { wsJsonToRes, validEmail, toast } from '../utils'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@@ -292,32 -293,31 +293,32 @@@ export class Login extends Component c.id == res.post.id); - found.my_vote = res.post.my_vote; - found.score = res.post.score; - found.upvotes = res.post.upvotes; - found.downvotes = res.post.downvotes; + } else if (res.op == UserOperation.CreatePostLike) { + let data = res.data as CreatePostLikeResponse; + let found = this.state.posts.find(c => c.id == data.post.id); + found.my_vote = data.post.my_vote; + found.score = data.post.score; + found.upvotes = data.post.upvotes; + found.downvotes = data.post.downvotes; this.setState(this.state); } } diff --cc ui/src/components/modlog.tsx index 6c35bce9,b2011af5..dd651092 --- a/ui/src/components/modlog.tsx +++ b/ui/src/components/modlog.tsx @@@ -17,7 -17,7 +17,7 @@@ import ModAdd, } from '../interfaces'; import { WebSocketService } from '../services'; - import { msgOp, addTypeInfo, fetchLimit, toast } from '../utils'; -import { wsJsonToRes, addTypeInfo, fetchLimit } from '../utils'; ++import { wsJsonToRes, addTypeInfo, fetchLimit, toast } from '../utils'; import { MomentTime } from './moment-time'; import moment from 'moment'; import { i18n } from '../i18next'; @@@ -422,17 -422,17 +422,17 @@@ export class Modlog extends Component !r.read); ++ } else if (res.op == UserOperation.GetPrivateMessages) { ++ let data = res.data as PrivateMessagesResponse; ++ let unreadMessages = data.messages.filter(r => !r.read); + if ( + unreadMessages.length > 0 && + this.state.fetchCount > 1 && + JSON.stringify(this.state.messages) !== JSON.stringify(unreadMessages) + ) { + this.notify(unreadMessages); + } + + this.state.messages = unreadMessages; + this.setState(this.state); + this.sendUnreadCount(); - } else if (op == UserOperation.GetSite) { - let res: GetSiteResponse = msg; + } else if (res.op == UserOperation.GetSite) { + let data = res.data as GetSiteResponse; - if (res.site) { - this.state.siteName = res.site.name; - WebSocketService.Instance.site = res.site; + if (data.site) { + this.state.siteName = data.site.name; + WebSocketService.Instance.site = data.site; this.setState(this.state); } } diff --cc ui/src/components/password_change.tsx index 76b4fb01,97f10888..10b6867c --- a/ui/src/components/password_change.tsx +++ b/ui/src/components/password_change.tsx @@@ -5,9 -5,10 +5,10 @@@ import UserOperation, LoginResponse, PasswordChangeForm, + WebSocketJsonResponse, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; - import { msgOp, capitalizeFirstLetter, toast } from '../utils'; -import { wsJsonToRes, capitalizeFirstLetter } from '../utils'; ++import { wsJsonToRes, capitalizeFirstLetter, toast } from '../utils'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@@ -133,10 -134,10 +134,10 @@@ export class PasswordChange extends Com WebSocketService.Instance.passwordChange(i.state.passwordChangeForm); } - parseMessage(msg: any) { - let op: UserOperation = msgOp(msg); + parseMessage(msg: WebSocketJsonResponse) { + let res = wsJsonToRes(msg); if (msg.error) { - alert(i18n.t(msg.error)); + toast(i18n.t(msg.error), 'danger'); this.state.loading = false; this.setState(this.state); return; diff --cc ui/src/components/post-form.tsx index 97a44094,454a569f..44061774 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@@ -458,10 -458,10 +459,10 @@@ export class PostForm extends Component }); } - parseMessage(msg: any) { - let op: UserOperation = msgOp(msg); - if (msg.error) { + parseMessage(msg: WebSocketJsonResponse) { + let res = wsJsonToRes(msg); + if (res.error) { - alert(i18n.t(res.error)); + toast(i18n.t(msg.error), 'danger'); this.state.loading = false; this.setState(this.state); return; diff --cc ui/src/components/post.tsx index 308fce85,1e334b1d..931ced2d --- a/ui/src/components/post.tsx +++ b/ui/src/components/post.tsx @@@ -26,9 -26,10 +26,10 @@@ import SearchResponse, GetSiteResponse, GetCommunityResponse, + WebSocketJsonResponse, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; - import { msgOp, hotRank, toast } from '../utils'; -import { wsJsonToRes, hotRank } from '../utils'; ++import { wsJsonToRes, hotRank, toast } from '../utils'; import { PostListing } from './post-listing'; import { PostListings } from './post-listings'; import { Sidebar } from './sidebar'; @@@ -341,19 -342,19 +342,19 @@@ export class Post extends Component c.id == res.comment.id); - found.content = res.comment.content; - found.updated = res.comment.updated; - found.removed = res.comment.removed; - found.deleted = res.comment.deleted; - found.upvotes = res.comment.upvotes; - found.downvotes = res.comment.downvotes; - found.score = res.comment.score; - found.read = res.comment.read; + } else if (res.op == UserOperation.EditComment) { + let data = res.data as CommentResponse; + let found = this.state.comments.find(c => c.id == data.comment.id); + found.content = data.comment.content; + found.updated = data.comment.updated; + found.removed = data.comment.removed; + found.deleted = data.comment.deleted; + found.upvotes = data.comment.upvotes; + found.downvotes = data.comment.downvotes; + found.score = data.comment.score; + found.read = data.comment.read; this.setState(this.state); - } else if (op == UserOperation.SaveComment) { - let res: CommentResponse = msg; - let found = this.state.comments.find(c => c.id == res.comment.id); - found.saved = res.comment.saved; + } else if (res.op == UserOperation.SaveComment) { + let data = res.data as CommentResponse; + let found = this.state.comments.find(c => c.id == data.comment.id); + found.saved = data.comment.saved; this.setState(this.state); - } else if (op == UserOperation.CreateCommentLike) { - let res: CommentResponse = msg; + } else if (res.op == UserOperation.CreateCommentLike) { + let data = res.data as CommentResponse; let found: Comment = this.state.comments.find( - c => c.id === res.comment.id + c => c.id === data.comment.id ); - found.score = res.comment.score; - found.upvotes = res.comment.upvotes; - found.downvotes = res.comment.downvotes; - if (res.comment.my_vote !== null) { - found.my_vote = res.comment.my_vote; + found.score = data.comment.score; + found.upvotes = data.comment.upvotes; + found.downvotes = data.comment.downvotes; - if (data.comment.my_vote !== null) found.my_vote = data.comment.my_vote; ++ if (data.comment.my_vote !== null) { ++ found.my_vote = data.comment.my_vote; + found.upvoteLoading = false; + found.downvoteLoading = false; + } this.setState(this.state); - } else if (op == UserOperation.CreatePostLike) { - let res: CreatePostLikeResponse = msg; - this.state.post.my_vote = res.post.my_vote; - this.state.post.score = res.post.score; - this.state.post.upvotes = res.post.upvotes; - this.state.post.downvotes = res.post.downvotes; - this.state.post.upvoteLoading = false; - this.state.post.downvoteLoading = false; + } else if (res.op == UserOperation.CreatePostLike) { + let data = res.data as CreatePostLikeResponse; + this.state.post.my_vote = data.post.my_vote; + this.state.post.score = data.post.score; + this.state.post.upvotes = data.post.upvotes; + this.state.post.downvotes = data.post.downvotes; this.setState(this.state); - } else if (op == UserOperation.EditPost) { - let res: PostResponse = msg; - this.state.post = res.post; + } else if (res.op == UserOperation.EditPost) { + let data = res.data as PostResponse; + this.state.post = data.post; this.setState(this.state); - } else if (op == UserOperation.SavePost) { - let res: PostResponse = msg; - this.state.post = res.post; + } else if (res.op == UserOperation.SavePost) { + let data = res.data as PostResponse; + this.state.post = data.post; this.setState(this.state); - } else if (op == UserOperation.EditCommunity) { - let res: CommunityResponse = msg; - this.state.community = res.community; - this.state.post.community_id = res.community.id; - this.state.post.community_name = res.community.name; + } else if (res.op == UserOperation.EditCommunity) { + let data = res.data as CommunityResponse; + this.state.community = data.community; + this.state.post.community_id = data.community.id; + this.state.post.community_name = data.community.name; this.setState(this.state); - } else if (op == UserOperation.FollowCommunity) { - let res: CommunityResponse = msg; - this.state.community.subscribed = res.community.subscribed; + } else if (res.op == UserOperation.FollowCommunity) { + let data = res.data as CommunityResponse; + this.state.community.subscribed = data.community.subscribed; this.state.community.number_of_subscribers = - res.community.number_of_subscribers; + data.community.number_of_subscribers; this.setState(this.state); - } else if (op == UserOperation.BanFromCommunity) { - let res: BanFromCommunityResponse = msg; + } else if (res.op == UserOperation.BanFromCommunity) { + let data = res.data as BanFromCommunityResponse; this.state.comments - .filter(c => c.creator_id == res.user.id) - .forEach(c => (c.banned_from_community = res.banned)); - if (this.state.post.creator_id == res.user.id) { - this.state.post.banned_from_community = res.banned; + .filter(c => c.creator_id == data.user.id) + .forEach(c => (c.banned_from_community = data.banned)); + if (this.state.post.creator_id == data.user.id) { + this.state.post.banned_from_community = data.banned; } this.setState(this.state); - } else if (op == UserOperation.AddModToCommunity) { - let res: AddModToCommunityResponse = msg; - this.state.moderators = res.moderators; + } else if (res.op == UserOperation.AddModToCommunity) { + let data = res.data as AddModToCommunityResponse; + this.state.moderators = data.moderators; this.setState(this.state); - } else if (op == UserOperation.BanUser) { - let res: BanUserResponse = msg; + } else if (res.op == UserOperation.BanUser) { + let data = res.data as BanUserResponse; this.state.comments - .filter(c => c.creator_id == res.user.id) - .forEach(c => (c.banned = res.banned)); - if (this.state.post.creator_id == res.user.id) { - this.state.post.banned = res.banned; + .filter(c => c.creator_id == data.user.id) + .forEach(c => (c.banned = data.banned)); + if (this.state.post.creator_id == data.user.id) { + this.state.post.banned = data.banned; } this.setState(this.state); - } else if (op == UserOperation.AddAdmin) { - let res: AddAdminResponse = msg; - this.state.admins = res.admins; + } else if (res.op == UserOperation.AddAdmin) { + let data = res.data as AddAdminResponse; + this.state.admins = data.admins; this.setState(this.state); - } else if (op == UserOperation.Search) { - let res: SearchResponse = msg; - this.state.crossPosts = res.posts.filter(p => p.id != this.state.post.id); + } else if (res.op == UserOperation.Search) { + let data = res.data as SearchResponse; + this.state.crossPosts = data.posts.filter( + p => p.id != this.state.post.id + ); this.setState(this.state); - } else if (op == UserOperation.TransferSite) { - let res: GetSiteResponse = msg; + } else if (res.op == UserOperation.TransferSite) { + let data = res.data as GetSiteResponse; - this.state.admins = res.admins; + this.state.admins = data.admins; this.setState(this.state); - } else if (op == UserOperation.TransferCommunity) { - let res: GetCommunityResponse = msg; - this.state.community = res.community; - this.state.moderators = res.moderators; - this.state.admins = res.admins; + } else if (res.op == UserOperation.TransferCommunity) { + let data = res.data as GetCommunityResponse; + this.state.community = data.community; + this.state.moderators = data.moderators; + this.state.admins = data.admins; this.setState(this.state); } } diff --cc ui/src/components/private-message-form.tsx index 96bd807d,00000000..5ee1c1fd mode 100644,000000..100644 --- a/ui/src/components/private-message-form.tsx +++ b/ui/src/components/private-message-form.tsx @@@ -1,292 -1,0 +1,293 @@@ +import { Component, linkEvent } from 'inferno'; +import { Link } from 'inferno-router'; +import { Subscription } from 'rxjs'; +import { retryWhen, delay, take } from 'rxjs/operators'; +import { + PrivateMessageForm as PrivateMessageFormI, + EditPrivateMessageForm, + PrivateMessageFormParams, + PrivateMessage, + PrivateMessageResponse, + UserView, + UserOperation, + UserDetailsResponse, + GetUserDetailsForm, + SortType, ++ WebSocketJsonResponse, +} from '../interfaces'; +import { WebSocketService } from '../services'; +import { - msgOp, + capitalizeFirstLetter, + markdownHelpUrl, + mdToHtml, + showAvatars, + pictshareAvatarThumbnail, ++ wsJsonToRes, + toast, +} from '../utils'; +import autosize from 'autosize'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; + +interface PrivateMessageFormProps { + privateMessage?: PrivateMessage; // If a pm is given, that means this is an edit + params?: PrivateMessageFormParams; + onCancel?(): any; + onCreate?(message: PrivateMessage): any; + onEdit?(message: PrivateMessage): any; +} + +interface PrivateMessageFormState { + privateMessageForm: PrivateMessageFormI; + recipient: UserView; + loading: boolean; + previewMode: boolean; + showDisclaimer: boolean; +} + +export class PrivateMessageForm extends Component< + PrivateMessageFormProps, + PrivateMessageFormState +> { + private subscription: Subscription; + private emptyState: PrivateMessageFormState = { + privateMessageForm: { + content: null, + recipient_id: null, + }, + recipient: null, + loading: false, + previewMode: false, + showDisclaimer: false, + }; + + constructor(props: any, context: any) { + super(props, context); + + this.state = this.emptyState; + + if (this.props.privateMessage) { + this.state.privateMessageForm = { + content: this.props.privateMessage.content, + recipient_id: this.props.privateMessage.recipient_id, + }; + } + + if (this.props.params) { + this.state.privateMessageForm.recipient_id = this.props.params.recipient_id; + let form: GetUserDetailsForm = { + user_id: this.state.privateMessageForm.recipient_id, + sort: SortType[SortType.New], + saved_only: false, + }; + WebSocketService.Instance.getUserDetails(form); + } + + 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') + ); + } + + componentDidMount() { + autosize(document.querySelectorAll('textarea')); + } + + componentWillUnmount() { + this.subscription.unsubscribe(); + } + + render() { + return ( +
+
+ {!this.props.privateMessage && ( +
+ + + {this.state.recipient && ( +
+ + {this.state.recipient.avatar && showAvatars() && ( + + )} + {this.state.recipient.name} + +
+ )} +
+ )} +
+ +
+