Support for editing posts

This commit is contained in:
Bnyro 2023-06-21 15:37:07 +02:00
parent 36d1c74625
commit b56776b671
7 changed files with 176 additions and 61 deletions

View File

@ -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<CommentResponse
super::post("/comment/like", &params)
}
pub fn edit_comment(
body: String,
comment_id: i32
) -> Result<CommentResponse, reqwest::Error> {
let params = EditComment {
content: Some(body),
comment_id: CommentId(comment_id),
auth: settings::get_current_account().jwt.unwrap(),
..Default::default()
};
super::put("/post", &params)
}
pub fn delete_comment(comment_id: CommentId) -> Result<CommentResponse, reqwest::Error> {
let params = DeleteComment {
comment_id,

View File

@ -53,3 +53,15 @@ where
.send()?
.json()
}
fn put<T, Params>(path: &str, params: &Params) -> Result<T, reqwest::Error>
where
T: DeserializeOwned,
Params: Serialize + std::fmt::Debug,
{
CLIENT
.put(&get_url(path))
.json(&params)
.send()?
.json()
}

View File

@ -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<reqwest::Url>,
community_id: i32,
) -> Result<PostResponse, reqwest::Error> {
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", &params)
}
pub fn edit_post(
name: String,
url: Option<reqwest::Url>,
body: String,
post_id: i32
) -> Result<PostResponse, reqwest::Error> {
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", &params)
}
// 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<PostResponse, reqwest::Error> {
let params = CreatePostLike {

View File

@ -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<DialogMsg> = MessageBroker::new();
pub struct CommunityPage {
info: CommunityView,
avatar: Controller<WebImage>,
posts: FactoryVecDeque<PostRow>,
#[allow(dead_code)]
create_post_dialog: Controller<CreatePostDialog>,
create_post_dialog: Controller<EditorDialog>,
}
#[derive(Debug)]
@ -21,10 +23,11 @@ pub enum CommunityInput {
UpdateCommunity(CommunityView),
DoneFetchPosts(Vec<PostView>),
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 => {}
}
}
}

View File

@ -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<DialogMsg> = MessageBroker::new();
pub struct PostPage {
info: GetPostResponse,
image: Controller<WebImage>,
@ -14,7 +16,7 @@ pub struct PostPage {
community_avatar: Controller<WebImage>,
comments: FactoryVecDeque<CommentRow>,
#[allow(dead_code)]
create_comment_dialog: Controller<CreatePostDialog>,
create_comment_dialog: Controller<EditorDialog>,
voting_row: Controller<VotingRowModel>
}
@ -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)]
@ -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;
}
}
}

View File

@ -1,13 +1,20 @@
use relm4::{prelude::*, MessageBroker};
use relm4::prelude::*;
use gtk::prelude::*;
pub static CREATE_POST_DIALOG_BROKER: MessageBroker<DialogMsg> = MessageBroker::new();
pub static CREATE_COMMENT_DIALOG_BROKER: MessageBroker<DialogMsg> = MessageBroker::new();
#[derive(Debug, Clone, Default)]
pub struct EditorData {
pub name: String,
pub body: String,
pub url: Option<reqwest::Url>,
}
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<Self>,
) -> ComponentParts<Self> {
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,11 +141,35 @@ impl SimpleComponent for CreatePostDialog {
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
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());
}
}
}
}

View File

@ -1 +1 @@
pub mod create_post;
pub mod editor;