summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorColin Reeder <colin@vpzom.click>2020-07-09 22:47:38 -0600
committerColin Reeder <colin@vpzom.click>2020-07-09 22:47:38 -0600
commit7e2262e128f3313c0ca9878a4507574b0d8ef0a4 (patch)
treee19b3be67dc9b43ba5b779a7d9c7319234b617bb
parent5e302b81be25f00635cea654f2c0ee9bac97a8ae (diff)
Improve redirects after actions (#49)
-rw-r--r--src/components/mod.rs60
-rw-r--r--src/routes/communities.rs66
-rw-r--r--src/routes/mod.rs95
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
}