From b56776b67169069af5e27d02e429cd288864b1ec Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 21 Jun 2023 15:37:07 +0200 Subject: [PATCH] Support for editing posts --- src/api/comment.rs | 16 +++- src/api/mod.rs | 12 +++ src/api/post.rs | 21 +++++- src/components/community_page.rs | 27 ++++--- src/components/post_page.rs | 69 +++++++++++++---- src/dialogs/{create_post.rs => editor.rs} | 90 +++++++++++++++-------- src/dialogs/mod.rs | 2 +- 7 files changed, 176 insertions(+), 61 deletions(-) rename src/dialogs/{create_post.rs => editor.rs} (57%) diff --git a/src/api/comment.rs b/src/api/comment.rs index 008fa0b..8771dbd 100644 --- a/src/api/comment.rs +++ b/src/api/comment.rs @@ -1,8 +1,7 @@ -use lemmy_api_common::{comment::{CommentResponse, CreateComment, CreateCommentLike, DeleteComment}, lemmy_db_schema::newtypes::{PostId, CommentId}}; +use lemmy_api_common::{comment::{CommentResponse, CreateComment, CreateCommentLike, DeleteComment, EditComment}, lemmy_db_schema::newtypes::{PostId, CommentId}}; use crate::settings; - pub fn create_comment( post_id: i32, content: String, @@ -28,6 +27,19 @@ pub fn like_comment(comment_id: CommentId, score: i16) -> Result Result { + let params = EditComment { + content: Some(body), + comment_id: CommentId(comment_id), + auth: settings::get_current_account().jwt.unwrap(), + ..Default::default() + }; + super::put("/post", ¶ms) +} + pub fn delete_comment(comment_id: CommentId) -> Result { let params = DeleteComment { comment_id, diff --git a/src/api/mod.rs b/src/api/mod.rs index 4810a93..803c84d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -53,3 +53,15 @@ where .send()? .json() } + +fn put(path: &str, params: &Params) -> Result +where + T: DeserializeOwned, + Params: Serialize + std::fmt::Debug, +{ + CLIENT + .put(&get_url(path)) + .json(¶ms) + .send()? + .json() +} diff --git a/src/api/post.rs b/src/api/post.rs index 00d98df..c016f03 100644 --- a/src/api/post.rs +++ b/src/api/post.rs @@ -1,4 +1,4 @@ -use lemmy_api_common::{post::{GetPost, GetPostResponse, PostResponse, CreatePost, CreatePostLike, DeletePost}, lemmy_db_schema::{newtypes::{PostId, CommunityId}, CommentSortType, ListingType}, comment::{GetComments, GetCommentsResponse}, lemmy_db_views::structs::CommentView}; +use lemmy_api_common::{post::{GetPost, GetPostResponse, PostResponse, CreatePost, CreatePostLike, DeletePost, EditPost}, lemmy_db_schema::{newtypes::{PostId, CommunityId}, CommentSortType, ListingType}, comment::{GetComments, GetCommentsResponse}, lemmy_db_views::structs::CommentView}; use crate::settings; @@ -36,11 +36,13 @@ pub fn default_post() -> GetPostResponse { pub fn create_post( name: String, body: String, + url: Option, community_id: i32, ) -> Result { let params = CreatePost { name, body: Some(body), + url, community_id: CommunityId(community_id), auth: settings::get_current_account().jwt.unwrap(), ..Default::default() @@ -48,6 +50,23 @@ pub fn create_post( super::post("/post", ¶ms) } +pub fn edit_post( + name: String, + url: Option, + body: String, + post_id: i32 +) -> Result { + let params = EditPost { + name: Some(name), + body: Some(body), + url, + post_id: PostId(post_id), + auth: settings::get_current_account().jwt.unwrap(), + ..Default::default() + }; + super::put("/post", ¶ms) +} + // for score, use 1 to upvote, -1 to vote down and 0 to reset the user's voting pub fn like_post(post_id: PostId, score: i16) -> Result { let params = CreatePostLike { diff --git a/src/components/community_page.rs b/src/components/community_page.rs index 816dcdf..660699a 100644 --- a/src/components/community_page.rs +++ b/src/components/community_page.rs @@ -1,6 +1,6 @@ -use crate::{util::markdown_to_pango_markup, dialogs::create_post::{CreatePostDialog, CREATE_POST_DIALOG_BROKER, DialogMsg, CreatePostDialogOutput, DialogType}}; +use crate::{util::markdown_to_pango_markup, dialogs::editor::{EditorDialog, DialogMsg, EditorOutput, DialogType, EditorData}}; use lemmy_api_common::{lemmy_db_views::structs::PostView, lemmy_db_views_actor::structs::CommunityView, lemmy_db_schema::SubscribedType}; -use relm4::{prelude::*, factory::FactoryVecDeque}; +use relm4::{prelude::*, factory::FactoryVecDeque, MessageBroker}; use gtk::prelude::*; use relm4_components::web_image::WebImage; @@ -8,12 +8,14 @@ use crate::{api, util::get_web_image_msg}; use super::post_row::PostRow; +static COMMUNITY_PAGE_BROKER: MessageBroker = MessageBroker::new(); + pub struct CommunityPage { info: CommunityView, avatar: Controller, posts: FactoryVecDeque, #[allow(dead_code)] - create_post_dialog: Controller, + create_post_dialog: Controller, } #[derive(Debug)] @@ -21,10 +23,11 @@ pub enum CommunityInput { UpdateCommunity(CommunityView), DoneFetchPosts(Vec), OpenCreatePostDialog, - CreatePostRequest(String, String), + CreatePostRequest(EditorData), CreatedPost(PostView), ToggleSubscription, - UpdateSubscriptionState(SubscribedType) + UpdateSubscriptionState(SubscribedType), + None } #[relm4::component(pub)] @@ -119,11 +122,12 @@ impl SimpleComponent for CommunityPage { let avatar = WebImage::builder().launch("".to_string()).detach(); let posts = FactoryVecDeque::new(gtk::Box::default(), sender.output_sender()); - let dialog = CreatePostDialog::builder() + let dialog = EditorDialog::builder() .transient_for(root) - .launch_with_broker(DialogType::Post, &CREATE_POST_DIALOG_BROKER) + .launch_with_broker(DialogType::Post, &COMMUNITY_PAGE_BROKER) .forward(sender.input_sender(), |msg| match msg { - CreatePostDialogOutput::CreateRequest(name, body) => CommunityInput::CreatePostRequest(name, body) + EditorOutput::CreateRequest(post) => CommunityInput::CreatePostRequest(post), + _ => CommunityInput::None }); let model = CommunityPage { info: init, avatar, posts, create_post_dialog: dialog }; @@ -155,15 +159,15 @@ impl SimpleComponent for CommunityPage { } } CommunityInput::OpenCreatePostDialog => { - CREATE_POST_DIALOG_BROKER.send(DialogMsg::Show) + COMMUNITY_PAGE_BROKER.send(DialogMsg::Show) } CommunityInput::CreatedPost(post) => { self.posts.guard().push_front(post); } - CommunityInput::CreatePostRequest(name, body) => { + CommunityInput::CreatePostRequest(post) => { let id = self.info.community.id.0.clone(); std::thread::spawn(move || { - let message = match api::post::create_post(name, body, id) { + let message = match api::post::create_post(post.name, post.body, post.url, id) { Ok(post) => Some(CommunityInput::CreatedPost(post.post_view)), Err(err) => { println!("{}", err.to_string()); None } }; @@ -187,6 +191,7 @@ impl SimpleComponent for CommunityPage { CommunityInput::UpdateSubscriptionState(state) => { self.info.subscribed = state; } + CommunityInput::None => {} } } } diff --git a/src/components/post_page.rs b/src/components/post_page.rs index d1033d1..e55ed47 100644 --- a/src/components/post_page.rs +++ b/src/components/post_page.rs @@ -1,12 +1,14 @@ -use lemmy_api_common::{lemmy_db_views::structs::CommentView, post::GetPostResponse}; -use relm4::{prelude::*, factory::FactoryVecDeque}; +use lemmy_api_common::{lemmy_db_views::structs::{CommentView, PostView}, post::GetPostResponse}; +use relm4::{prelude::*, factory::FactoryVecDeque, MessageBroker}; use gtk::prelude::*; use relm4_components::web_image::WebImage; -use crate::{api, util::{get_web_image_msg, get_web_image_url, markdown_to_pango_markup}, dialogs::create_post::{CreatePostDialog, CreatePostDialogOutput, DialogMsg, CREATE_COMMENT_DIALOG_BROKER, DialogType}, settings}; +use crate::{api, util::{get_web_image_msg, get_web_image_url, markdown_to_pango_markup}, dialogs::editor::{EditorDialog, EditorOutput, DialogMsg, DialogType, EditorData}, settings}; use super::{comment_row::CommentRow, voting_row::{VotingRowModel, VotingStats, VotingRowInput}}; +pub static POST_PAGE_BROKER: MessageBroker = MessageBroker::new(); + pub struct PostPage { info: GetPostResponse, image: Controller, @@ -14,7 +16,7 @@ pub struct PostPage { community_avatar: Controller, comments: FactoryVecDeque, #[allow(dead_code)] - create_comment_dialog: Controller, + create_comment_dialog: Controller, voting_row: Controller } @@ -26,10 +28,12 @@ pub enum PostInput { OpenCommunity, OpenLink, OpenCreateCommentDialog, - CreateCommentRequest(String), + CreateCommentRequest(EditorData), + EditPostRequest(EditorData), CreatedComment(CommentView), - DeletePost, EditPost, + DeletePost, + DoneEditPost(PostView) } #[relm4::component(pub)] @@ -71,7 +75,7 @@ impl SimpleComponent for PostPage { set_halign: gtk::Align::Center, gtk::Label { - set_text: "posted by " + set_text: "posted by" }, if model.info.post_view.creator.avatar.is_some() { @@ -118,6 +122,16 @@ impl SimpleComponent for PostPage { connect_clicked => PostInput::OpenLink, }, + if model.info.post_view.creator.id.0 == settings::get_current_account().id { + gtk::Button { + set_icon_name: "document-edit", + connect_clicked => PostInput::EditPost, + set_margin_start: 10, + } + } else { + gtk::Box {} + }, + if model.info.post_view.creator.id.0 == settings::get_current_account().id { gtk::Button { set_icon_name: "edit-delete", @@ -169,11 +183,12 @@ impl SimpleComponent for PostPage { let comments = FactoryVecDeque::new(gtk::Box::default(), sender.output_sender()); let creator_avatar = WebImage::builder().launch("".to_string()).detach(); let community_avatar = WebImage::builder().launch("".to_string()).detach(); - let dialog = CreatePostDialog::builder() + let dialog = EditorDialog::builder() .transient_for(root) - .launch_with_broker(DialogType::Comment, &CREATE_COMMENT_DIALOG_BROKER) + .launch_with_broker(DialogType::Comment, &POST_PAGE_BROKER) .forward(sender.input_sender(), |msg| match msg { - CreatePostDialogOutput::CreateRequest(_name, body) => PostInput::CreateCommentRequest(body) + EditorOutput::CreateRequest(comment) => PostInput::CreateCommentRequest(comment), + EditorOutput::EditRequest(post) => PostInput::EditPostRequest(post) }); let voting_row = VotingRowModel::builder().launch(VotingStats::default()).detach(); let model = PostPage { info: init, image, comments, creator_avatar, community_avatar, create_comment_dialog: dialog, voting_row }; @@ -234,19 +249,19 @@ impl SimpleComponent for PostPage { gtk::show_uri(None::<&relm4::gtk::Window>, &link, 0); } PostInput::OpenCreateCommentDialog => { - CREATE_COMMENT_DIALOG_BROKER.send(DialogMsg::Show) + POST_PAGE_BROKER.send(DialogMsg::Show) } PostInput::CreatedComment(comment) => { self.comments.guard().push_front(comment); } - PostInput::CreateCommentRequest(body) => { + PostInput::CreateCommentRequest(post) => { let id = self.info.post_view.post.id.0; std::thread::spawn(move || { - let message = match api::comment::create_comment(id, body, None) { + let message = match api::comment::create_comment(id, post.body, None) { Ok(comment) => Some(PostInput::CreatedComment(comment.comment_view)), Err(err) => { println!("{}", err.to_string()); None } }; - if message.is_some() { sender.input(message.unwrap()) }; + if let Some(message) = message { sender.input(message) }; }); } PostInput::DeletePost => { @@ -257,7 +272,31 @@ impl SimpleComponent for PostPage { }); } PostInput::EditPost => { - + let url = match self.info.post_view.post.url.clone() { + Some(url) => url.to_string(), + None => String::from("") + }; + let data = EditorData { + name: self.info.post_view.post.name.clone(), + body: self.info.post_view.post.body.clone().unwrap_or(String::from("")), + url: reqwest::Url::parse(&url).ok(), + }; + POST_PAGE_BROKER.send(DialogMsg::UpdateData(data)); + POST_PAGE_BROKER.send(DialogMsg::UpdateType(DialogType::Post, false)); + POST_PAGE_BROKER.send(DialogMsg::Show) + } + PostInput::EditPostRequest(post) => { + let id = self.info.post_view.post.id.0; + std::thread::spawn(move || { + let message = match api::post::edit_post(post.name, post.url, post.body, id) { + Ok(post) => Some(PostInput::DoneEditPost(post.post_view)), + Err(err) => { println!("{}", err.to_string()); None } + }; + if let Some(message) = message { sender.input(message) }; + }); + } + PostInput::DoneEditPost(post) => { + self.info.post_view = post; } } } diff --git a/src/dialogs/create_post.rs b/src/dialogs/editor.rs similarity index 57% rename from src/dialogs/create_post.rs rename to src/dialogs/editor.rs index ce4f110..e3cab61 100644 --- a/src/dialogs/create_post.rs +++ b/src/dialogs/editor.rs @@ -1,13 +1,20 @@ -use relm4::{prelude::*, MessageBroker}; +use relm4::prelude::*; use gtk::prelude::*; -pub static CREATE_POST_DIALOG_BROKER: MessageBroker = MessageBroker::new(); -pub static CREATE_COMMENT_DIALOG_BROKER: MessageBroker = MessageBroker::new(); +#[derive(Debug, Clone, Default)] +pub struct EditorData { + pub name: String, + pub body: String, + pub url: Option, +} - -pub struct CreatePostDialog { +pub struct EditorDialog { type_: DialogType, + is_new: bool, visible: bool, + name_buffer: gtk::EntryBuffer, + url_buffer: gtk::EntryBuffer, + body_buffer: gtk::TextBuffer, } #[derive(Debug, Clone, Copy)] @@ -20,19 +27,22 @@ pub enum DialogType { pub enum DialogMsg { Show, Hide, - Okay(String, String) + UpdateType(DialogType, bool), + UpdateData(EditorData), + Okay } #[derive(Debug)] -pub enum CreatePostDialogOutput { - CreateRequest(String, String) +pub enum EditorOutput { + CreateRequest(EditorData), + EditRequest(EditorData) } #[relm4::component(pub)] -impl SimpleComponent for CreatePostDialog { +impl SimpleComponent for EditorDialog { type Init = DialogType; type Input = DialogMsg; - type Output = CreatePostDialogOutput; + type Output = EditorOutput; view! { dialog = gtk::Dialog { @@ -57,11 +67,17 @@ impl SimpleComponent for CreatePostDialog { set_label: "Create post", add_css_class: "font-bold" }, - #[name(name)] gtk::Entry { set_placeholder_text: Some("Title"), set_margin_top: 10, set_margin_bottom: 10, + set_buffer: &model.name_buffer, + }, + gtk::Entry { + set_placeholder_text: Some("Url"), + set_margin_top: 10, + set_margin_bottom: 10, + set_buffer: &model.url_buffer, }, } } @@ -97,22 +113,7 @@ impl SimpleComponent for CreatePostDialog { }, gtk::Button { set_label: "Okay", - connect_clicked[sender, name, body] => move |_| { - let name = name.text().to_string(); - let body_buffer = body.buffer(); - let (start, end) = &body_buffer.bounds(); - let body = body_buffer.text(start, end, true).to_string(); - match model.type_ { - DialogType::Post => { - if name.is_empty() || body.is_empty() { return; } - sender.input(DialogMsg::Okay(name, body)) - } - DialogType::Comment => { - if name.is_empty() { return; } - sender.input(DialogMsg::Okay(name, body)) - } - } - }, + connect_clicked => DialogMsg::Okay, } } }, @@ -129,7 +130,10 @@ impl SimpleComponent for CreatePostDialog { root: &Self::Root, sender: ComponentSender, ) -> ComponentParts { - let model = CreatePostDialog { type_: init, visible: false }; + let name_buffer = gtk::EntryBuffer::builder().build(); + let url_buffer = gtk::EntryBuffer::builder().build(); + let body_buffer = gtk::TextBuffer::builder().build(); + let model = EditorDialog { type_: init, visible: false, is_new: true, name_buffer, url_buffer, body_buffer }; let widgets = view_output!(); ComponentParts { model, widgets } } @@ -137,10 +141,34 @@ impl SimpleComponent for CreatePostDialog { fn update(&mut self, msg: Self::Input, sender: ComponentSender) { match msg { DialogMsg::Show => self.visible = true, - DialogMsg::Hide => self.visible = false, - DialogMsg::Okay(name, body) => { - let _ = sender.output(CreatePostDialogOutput::CreateRequest(name, body)); + DialogMsg::Hide => { + self.name_buffer.set_text(""); + self.url_buffer.set_text(""); + self.body_buffer.set_text(""); self.visible = false; + }, + DialogMsg::Okay => { + let name = self.name_buffer.text().to_string(); + let url = self.url_buffer.text().to_string(); + let (start, end) = &self.body_buffer.bounds(); + let body = self.body_buffer.text(start, end, true).to_string(); + let url = reqwest::Url::parse(&url).ok(); + let post = EditorData { name, body, url }; + let message = match self.is_new { + true => EditorOutput::CreateRequest(post), + false => EditorOutput::EditRequest(post) + }; + let _ = sender.output(message); + self.visible = false; + } + DialogMsg::UpdateType(type_, is_new) => { + self.type_ = type_; + self.is_new = is_new; + } + DialogMsg::UpdateData(data) => { + self.name_buffer.set_text(data.name); + if let Some(url) = data.url { self.url_buffer.set_text(url.to_string()); } + self.body_buffer.set_text(&data.body.clone()); } } } diff --git a/src/dialogs/mod.rs b/src/dialogs/mod.rs index 8cb9102..3822e56 100644 --- a/src/dialogs/mod.rs +++ b/src/dialogs/mod.rs @@ -1 +1 @@ -pub mod create_post; \ No newline at end of file +pub mod editor; \ No newline at end of file