diff options
-rw-r--r-- | src/components/mod.rs | 60 | ||||
-rw-r--r-- | src/routes/communities.rs | 66 | ||||
-rw-r--r-- | src/routes/mod.rs | 95 |
3 files changed, 165 insertions, 56 deletions
diff --git a/src/components/mod.rs b/src/components/mod.rs index cd348ba..a44bfb8 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,4 +1,5 @@ -use std::borrow::Cow; +use std::borrow::{Borrow, Cow}; +use std::collections::HashMap; use crate::resp_types::{ RespMinimalAuthorInfo, RespMinimalCommunityInfo, RespPostCommentInfo, RespPostListPost, @@ -260,7 +261,28 @@ impl<'user> render::Render for UserLink<'user> { } } -fn maybe_fill_value<'a>(values: &'a Option<&'a serde_json::Value>, name: &str) -> &'a str { +pub trait GetIndex<K, V> { + fn get<'a>(&'a self, key: K) -> Option<&'a V>; +} + +impl<K: Borrow<Q> + Eq + std::hash::Hash, V, Q: ?Sized + Eq + std::hash::Hash> GetIndex<&Q, V> + for HashMap<K, V> +{ + fn get<'a>(&'a self, key: &Q) -> Option<&'a V> { + HashMap::get(self, key) + } +} + +impl<I: serde_json::value::Index> GetIndex<I, serde_json::Value> for serde_json::Value { + fn get<'a>(&'a self, key: I) -> Option<&'a serde_json::Value> { + self.get(key) + } +} + +fn maybe_fill_value<'a, 'b, M: GetIndex<&'b str, serde_json::Value>>( + values: &'a Option<&'a M>, + name: &'b str, +) -> &'a str { values .and_then(|values| values.get(name)) .and_then(serde_json::Value::as_str) @@ -268,24 +290,38 @@ fn maybe_fill_value<'a>(values: &'a Option<&'a serde_json::Value>, name: &str) - } #[render::component] -pub fn MaybeFillInput<'a>( - values: &'a Option<&'a serde_json::Value>, +pub fn MaybeFillInput<'a, M: GetIndex<&'a str, serde_json::Value>>( + values: &'a Option<&'a M>, r#type: &'a str, name: &'a str, required: bool, ) { - render::rsx! { - <input - r#type - name - value={maybe_fill_value(values, name)} - required={if required { "true" } else { "false" }} - /> + let value = maybe_fill_value(values, name); + if required { + render::rsx! { + <input + r#type + name + value + required={""} + /> + } + } else { + render::rsx! { + <input + r#type + name + value + /> + } } } #[render::component] -pub fn MaybeFillTextArea<'a>(values: &'a Option<&'a serde_json::Value>, name: &'a str) { +pub fn MaybeFillTextArea<'a, M: GetIndex<&'a str, serde_json::Value>>( + values: &'a Option<&'a M>, + name: &'a str, +) { render::rsx! { <textarea name> {maybe_fill_value(values, name)} diff --git a/src/routes/communities.rs b/src/routes/communities.rs index aec64d2..4e6d694 100644 --- a/src/routes/communities.rs +++ b/src/routes/communities.rs @@ -1,10 +1,12 @@ -use crate::components::{CommunityLink, HTPage, PostItem}; +use crate::components::{CommunityLink, HTPage, MaybeFillInput, MaybeFillTextArea, PostItem}; use crate::resp_types::{ RespCommunityInfoMaybeYour, RespMinimalCommunityInfo, RespPostListPost, RespYourFollow, }; use crate::routes::{ - fetch_base_data, get_cookie_map, get_cookie_map_for_req, html_response, res_to_error, with_auth, + fetch_base_data, get_cookie_map, get_cookie_map_for_req, html_response, res_to_error, + with_auth, CookieMap, }; +use serde_derive::Deserialize; use std::collections::HashMap; use std::sync::Arc; @@ -258,6 +260,16 @@ async fn page_community_new_post( let cookies = get_cookie_map_for_req(&req)?; + page_community_new_post_inner(community_id, &cookies, ctx, None, None).await +} + +async fn page_community_new_post_inner( + community_id: i64, + cookies: &CookieMap<'_>, + ctx: Arc<crate::RouteContext>, + display_error: Option<String>, + prev_values: Option<&HashMap<&str, serde_json::Value>>, +) -> Result<hyper::Response<hyper::Body>, crate::Error> { let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, &cookies).await?; let submit_url = format!("/communities/{}/new_post/submit", community_id); @@ -265,22 +277,29 @@ async fn page_community_new_post( Ok(html_response(render::html! { <HTPage base_data={&base_data} title={"New Post"}> <h1>{"New Post"}</h1> + { + display_error.map(|msg| { + render::rsx! { + <div class={"errorBox"}>{msg}</div> + } + }) + } <form method={"POST"} action={&submit_url}> <div> <label> - {"Title: "}<input r#type={"text"} name={"title"} required={"true"} /> + {"Title: "}<MaybeFillInput values={&prev_values} r#type={"text"} name={"title"} required={true} /> </label> </div> <div> <label> - {"URL: "}<input r#type={"text"} name={"href"} /> + {"URL: "}<MaybeFillInput values={&prev_values} r#type={"text"} name={"href"} required={false} /> </label> </div> <div> <label> {"Text:"} <br /> - <textarea name={"content_text"}>{""}</textarea> + <MaybeFillTextArea values={&prev_values} name={"content_text"} /> </label> </div> <div> @@ -317,26 +336,39 @@ async fn handler_communities_new_post_submit( if body.get("href").and_then(|x| x.as_str()) == Some("") { body.remove("href"); } - let body = serde_json::to_vec(&body)?; - res_to_error( + let api_res = res_to_error( ctx.http_client .request(with_auth( hyper::Request::post(format!("{}/api/unstable/posts", ctx.backend_host)) - .body(body.into())?, + .body(serde_json::to_vec(&body)?.into())?, &cookies, )?) .await?, ) - .await?; - - Ok(hyper::Response::builder() - .status(hyper::StatusCode::SEE_OTHER) - .header( - hyper::header::LOCATION, - format!("/communities/{}", community_id), - ) - .body("Successfully posted.".into())?) + .await; + + match api_res { + Ok(api_res) => { + #[derive(Deserialize)] + struct PostsCreateResponse { + id: i64, + } + + let api_res = hyper::body::to_bytes(api_res.into_body()).await?; + let api_res: PostsCreateResponse = serde_json::from_slice(&api_res)?; + + Ok(hyper::Response::builder() + .status(hyper::StatusCode::SEE_OTHER) + .header(hyper::header::LOCATION, format!("/posts/{}", api_res.id)) + .body("Successfully posted.".into())?) + } + Err(crate::Error::RemoteError((_, message))) => { + page_community_new_post_inner(community_id, &cookies, ctx, Some(message), Some(&body)) + .await + } + Err(other) => Err(other), + } } pub fn route_communities() -> crate::RouteNode<()> { diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 177fd1f..c27b4a2 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -15,6 +15,11 @@ mod r#static; const COOKIE_AGE: u32 = 60 * 60 * 24 * 365; +#[derive(Deserialize)] +struct ReturnToParams<'a> { + return_to: Option<Cow<'a, str>>, +} + type CookieMap<'a> = std::collections::HashMap<&'a str, ginger::Cookie<'a>>; fn get_cookie_map<'a>(src: Option<&'a str>) -> Result<CookieMap<'a>, ginger::ParseError> { @@ -252,17 +257,22 @@ async fn page_comment_delete( let cookies = get_cookie_map_for_req(&req)?; - page_comment_delete_inner(comment_id, ctx, &cookies, None).await + page_comment_delete_inner(comment_id, ctx, &req.headers(), &cookies, None).await } async fn page_comment_delete_inner( comment_id: i64, ctx: Arc<crate::RouteContext>, + headers: &hyper::header::HeaderMap, cookies: &CookieMap<'_>, display_error: Option<String>, ) -> Result<hyper::Response<hyper::Body>, crate::Error> { let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, &cookies).await?; + let referer = headers + .get(hyper::header::REFERER) + .and_then(|x| x.to_str().ok()); + let api_res = res_to_error( ctx.http_client .request(with_auth( @@ -296,6 +306,15 @@ async fn page_comment_delete_inner( }) } <form method={"POST"} action={format!("/comments/{}/delete/confirm", comment.id)}> + { + if let Some(referer) = referer { + Some(render::rsx! { + <input type={"hidden"} name={"return_to"} value={referer} /> + }) + } else { + None + } + } <a href={format!("/comments/{}/", comment.id)}>{"No, cancel"}</a> {" "} <button r#type={"submit"}>{"Yes, delete"}</button> @@ -312,7 +331,12 @@ async fn handler_comment_delete_confirm( ) -> Result<hyper::Response<hyper::Body>, crate::Error> { let (comment_id,) = params; - let cookies = get_cookie_map_for_req(&req)?; + let (req_parts, body) = req.into_parts(); + + let cookies = get_cookie_map_for_headers(&req_parts.headers)?; + + let body = hyper::body::to_bytes(body).await?; + let body: ReturnToParams = serde_urlencoded::from_bytes(&body)?; let api_res = res_to_error( ctx.http_client @@ -331,10 +355,18 @@ async fn handler_comment_delete_confirm( match api_res { Ok(_) => Ok(hyper::Response::builder() .status(hyper::StatusCode::SEE_OTHER) - .header(hyper::header::LOCATION, "/") + .header( + hyper::header::LOCATION, + if let Some(return_to) = &body.return_to { + &return_to + } else { + "/" + }, + ) .body("Successfully deleted.".into())?), Err(crate::Error::RemoteError((status, message))) if status.is_client_error() => { - page_comment_delete_inner(comment_id, ctx, &cookies, Some(message)).await + page_comment_delete_inner(comment_id, ctx, &req_parts.headers, &cookies, Some(message)) + .await } Err(other) => Err(other), } @@ -349,6 +381,11 @@ async fn handler_comment_like( let cookies = get_cookie_map_for_req(&req)?; + let referer = req + .headers() + .get(hyper::header::REFERER) + .and_then(|x| x.to_str().ok()); + res_to_error( ctx.http_client .request(with_auth( @@ -365,7 +402,15 @@ async fn handler_comment_like( Ok(hyper::Response::builder() .status(hyper::StatusCode::SEE_OTHER) - .header(hyper::header::LOCATION, format!("/comments/{}", comment_id)) + .header( + hyper::header::LOCATION, + (if let Some(referer) = referer { + Cow::Borrowed(referer) + } else { + format!("/comments/{}", comment_id).into() + }) + .as_ref(), + ) .body("Successfully liked.".into())?) } @@ -378,6 +423,11 @@ async fn handler_comment_unlike( let cookies = get_cookie_map_for_req(&req)?; + let referer = req + .headers() + .get(hyper::header::REFERER) + .and_then(|x| x.to_str().ok()); + res_to_error( ctx.http_client .request(with_auth( @@ -394,7 +444,15 @@ async fn handler_comment_unlike( Ok(hyper::Response::builder() .status(hyper::StatusCode::SEE_OTHER) - .header(hyper::header::LOCATION, format!("/comments/{}", comment_id)) + .header( + hyper::header::LOCATION, + (if let Some(referer) = referer { + Cow::Borrowed(referer) + } else { + format!("/comments/{}", comment_id).into() + }) + .as_ref(), + ) .body("Successfully unliked.".into())?) } @@ -403,15 +461,6 @@ async fn handler_comment_submit_reply( ctx: Arc<crate::RouteContext>, req: hyper::Request<hyper::Body>, ) -> Result<hyper::Response<hyper::Body>, crate::Error> { - #[derive(Deserialize)] - struct CommentsRepliesCreateResponsePost { - id: i64, - } - #[derive(Deserialize)] - struct CommentsRepliesCreateResponse { - post: CommentsRepliesCreateResponsePost, - } - let (comment_id,) = params; let (req_parts, body) = req.into_parts(); @@ -436,18 +485,10 @@ async fn handler_comment_submit_reply( .await; match api_res { - Ok(api_res) => { - let api_res = hyper::body::to_bytes(api_res.into_body()).await?; - let api_res: CommentsRepliesCreateResponse = serde_json::from_slice(&api_res)?; - - Ok(hyper::Response::builder() - .status(hyper::StatusCode::SEE_OTHER) - .header( - hyper::header::LOCATION, - format!("/posts/{}", api_res.post.id), - ) - .body("Successfully posted.".into())?) - } + Ok(_) => Ok(hyper::Response::builder() + .status(hyper::StatusCode::SEE_OTHER) + .header(hyper::header::LOCATION, format!("/comments/{}", comment_id)) + .body("Successfully posted.".into())?), Err(crate::Error::RemoteError((status, message))) if status.is_client_error() => { page_comment_inner(comment_id, &cookies, ctx, Some(message), Some(&body)).await } |