Use HX-Target, not HX-Boost for Layout render
HX-Boost is not always sent in all AJAX requests that htmx sends, only those initiated by an element with hx-boost enabled. It was not showing up on requests following an HX-Redirect response. After reading the docs more, I realized HX-Target was what I wanted. If I can see that the request is targeting `#main-content` then I know to only return HTML inside that element. Simple.
This commit is contained in:
parent
bea3529e22
commit
a72bfa15bd
@ -8,7 +8,7 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::htmx::HXBoosted;
|
use crate::htmx::HXTarget;
|
||||||
use crate::models::entry::Entry;
|
use crate::models::entry::Entry;
|
||||||
use crate::partials::layout::Layout;
|
use crate::partials::layout::Layout;
|
||||||
use crate::uuid::Base62Uuid;
|
use crate::uuid::Base62Uuid;
|
||||||
@ -17,7 +17,7 @@ pub async fn get(
|
|||||||
Path(id): Path<Base62Uuid>,
|
Path(id): Path<Base62Uuid>,
|
||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
State(config): State<Config>,
|
State(config): State<Config>,
|
||||||
hx_boosted: Option<TypedHeader<HXBoosted>>,
|
hx_target: Option<TypedHeader<HXTarget>>,
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let entry = Entry::get(&pool, id.as_uuid()).await?;
|
let entry = Entry::get(&pool, id.as_uuid()).await?;
|
||||||
@ -30,7 +30,7 @@ pub async fn get(
|
|||||||
let content = fs::read_to_string(content_path).unwrap_or_else(|_| "No content".to_string());
|
let content = fs::read_to_string(content_path).unwrap_or_else(|_| "No content".to_string());
|
||||||
Ok(layout
|
Ok(layout
|
||||||
.with_subtitle(&title)
|
.with_subtitle(&title)
|
||||||
.boosted(hx_boosted)
|
.targeted(hx_target)
|
||||||
.render(html! {
|
.render(html! {
|
||||||
article {
|
article {
|
||||||
header {
|
header {
|
||||||
|
@ -16,7 +16,7 @@ use tokio_stream::StreamExt;
|
|||||||
use crate::actors::crawl_scheduler::{CrawlSchedulerHandle, CrawlSchedulerHandleMessage};
|
use crate::actors::crawl_scheduler::{CrawlSchedulerHandle, CrawlSchedulerHandleMessage};
|
||||||
use crate::actors::feed_crawler::FeedCrawlerHandleMessage;
|
use crate::actors::feed_crawler::FeedCrawlerHandleMessage;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::htmx::HXBoosted;
|
use crate::htmx::HXTarget;
|
||||||
use crate::models::entry::{Entry, GetEntriesOptions};
|
use crate::models::entry::{Entry, GetEntriesOptions};
|
||||||
use crate::models::feed::{CreateFeed, Feed};
|
use crate::models::feed::{CreateFeed, Feed};
|
||||||
use crate::partials::add_feed_form::add_feed_form;
|
use crate::partials::add_feed_form::add_feed_form;
|
||||||
@ -28,7 +28,7 @@ use crate::uuid::Base62Uuid;
|
|||||||
pub async fn get(
|
pub async fn get(
|
||||||
Path(id): Path<Base62Uuid>,
|
Path(id): Path<Base62Uuid>,
|
||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
hx_boosted: Option<TypedHeader<HXBoosted>>,
|
hx_target: Option<TypedHeader<HXTarget>>,
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let feed = Feed::get(&pool, id.as_uuid()).await?;
|
let feed = Feed::get(&pool, id.as_uuid()).await?;
|
||||||
@ -39,7 +39,7 @@ pub async fn get(
|
|||||||
let title = feed.title.unwrap_or_else(|| "Untitled Feed".to_string());
|
let title = feed.title.unwrap_or_else(|| "Untitled Feed".to_string());
|
||||||
let entries = Entry::get_all(&pool, &options).await?;
|
let entries = Entry::get_all(&pool, &options).await?;
|
||||||
let delete_url = format!("/feed/{}/delete", id);
|
let delete_url = format!("/feed/{}/delete", id);
|
||||||
Ok(layout.with_subtitle(&title).boosted(hx_boosted).render(html! {
|
Ok(layout.with_subtitle(&title).targeted(hx_target).render(html! {
|
||||||
header class="feed-header" {
|
header class="feed-header" {
|
||||||
h2 { (title) }
|
h2 { (title) }
|
||||||
button class="edit-feed" { "✏️ Edit feed" }
|
button class="edit-feed" { "✏️ Edit feed" }
|
||||||
|
@ -5,7 +5,7 @@ use maud::html;
|
|||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::htmx::HXBoosted;
|
use crate::htmx::HXTarget;
|
||||||
use crate::models::feed::{Feed, GetFeedsOptions};
|
use crate::models::feed::{Feed, GetFeedsOptions};
|
||||||
use crate::partials::add_feed_form::add_feed_form;
|
use crate::partials::add_feed_form::add_feed_form;
|
||||||
use crate::partials::feed_list::feed_list;
|
use crate::partials::feed_list::feed_list;
|
||||||
@ -14,14 +14,14 @@ use crate::partials::opml_import_form::opml_import_form;
|
|||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
hx_boosted: Option<TypedHeader<HXBoosted>>,
|
hx_target: Option<TypedHeader<HXTarget>>,
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let options = GetFeedsOptions::default();
|
let options = GetFeedsOptions::default();
|
||||||
let feeds = Feed::get_all(&pool, &options).await?;
|
let feeds = Feed::get_all(&pool, &options).await?;
|
||||||
Ok(layout
|
Ok(layout
|
||||||
.with_subtitle("feeds")
|
.with_subtitle("feeds")
|
||||||
.boosted(hx_boosted)
|
.targeted(hx_target)
|
||||||
.render(html! {
|
.render(html! {
|
||||||
header { h2 { "Feeds" } }
|
header { h2 { "Feeds" } }
|
||||||
div class="feeds" {
|
div class="feeds" {
|
||||||
|
@ -5,18 +5,18 @@ use maud::html;
|
|||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::htmx::HXBoosted;
|
use crate::htmx::HXTarget;
|
||||||
use crate::models::entry::Entry;
|
use crate::models::entry::Entry;
|
||||||
use crate::partials::{entry_list::entry_list, layout::Layout};
|
use crate::partials::{entry_list::entry_list, layout::Layout};
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
hx_boosted: Option<TypedHeader<HXBoosted>>,
|
hx_target: Option<TypedHeader<HXTarget>>,
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let options = Default::default();
|
let options = Default::default();
|
||||||
let entries = Entry::get_all(&pool, &options).await?;
|
let entries = Entry::get_all(&pool, &options).await?;
|
||||||
Ok(layout.boosted(hx_boosted).render(html! {
|
Ok(layout.targeted(hx_target).render(html! {
|
||||||
ul class="entries" {
|
ul class="entries" {
|
||||||
(entry_list(entries, &options))
|
(entry_list(entries, &options))
|
||||||
}
|
}
|
||||||
|
@ -18,15 +18,15 @@ use tokio_stream::Stream;
|
|||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::htmx::HXBoosted;
|
use crate::htmx::HXTarget;
|
||||||
use crate::log::MEM_LOG;
|
use crate::log::MEM_LOG;
|
||||||
use crate::partials::layout::Layout;
|
use crate::partials::layout::Layout;
|
||||||
|
|
||||||
pub async fn get(hx_boosted: Option<TypedHeader<HXBoosted>>, layout: Layout) -> Result<Response> {
|
pub async fn get(hx_target: Option<TypedHeader<HXTarget>>, layout: Layout) -> Result<Response> {
|
||||||
let mem_buf = MEM_LOG.lock().unwrap();
|
let mem_buf = MEM_LOG.lock().unwrap();
|
||||||
Ok(layout
|
Ok(layout
|
||||||
.with_subtitle("log")
|
.with_subtitle("log")
|
||||||
.boosted(hx_boosted)
|
.targeted(hx_target)
|
||||||
.render(html! {
|
.render(html! {
|
||||||
pre id="log" hx-sse="connect:/log/stream swap:message" hx-swap="beforeend" hx-target="#log" {
|
pre id="log" hx-sse="connect:/log/stream swap:message" hx-swap="beforeend" hx-target="#log" {
|
||||||
(PreEscaped(convert_escaped(from_utf8(mem_buf.as_slices().0).unwrap()).unwrap()))
|
(PreEscaped(convert_escaped(from_utf8(mem_buf.as_slices().0).unwrap()).unwrap()))
|
||||||
|
@ -8,7 +8,7 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::auth::verify_password;
|
use crate::auth::verify_password;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::htmx::{HXBoosted, HXRedirect};
|
use crate::htmx::{HXRedirect, HXTarget};
|
||||||
use crate::partials::login_form::{login_form, LoginFormProps};
|
use crate::partials::login_form::{login_form, LoginFormProps};
|
||||||
use crate::{
|
use crate::{
|
||||||
models::user::{AuthContext, User},
|
models::user::{AuthContext, User},
|
||||||
@ -22,36 +22,49 @@ pub struct Login {
|
|||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(hx_boosted: Option<TypedHeader<HXBoosted>>, layout: Layout) -> Result<Response> {
|
pub fn login_page(
|
||||||
Ok(layout
|
hx_target: Option<TypedHeader<HXTarget>>,
|
||||||
|
layout: Layout,
|
||||||
|
form_props: LoginFormProps,
|
||||||
|
) -> Response {
|
||||||
|
layout
|
||||||
.with_subtitle("login")
|
.with_subtitle("login")
|
||||||
.boosted(hx_boosted)
|
.targeted(hx_target)
|
||||||
.render(html! {
|
.render(html! {
|
||||||
div class="center-horizontal" {
|
div class="center-horizontal" {
|
||||||
header class="center-text" {
|
header class="center-text" {
|
||||||
h2 { "Login" }
|
h2 { "Login" }
|
||||||
}
|
}
|
||||||
(login_form(LoginFormProps::default()))
|
(login_form(form_props))
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(hx_target: Option<TypedHeader<HXTarget>>, layout: Layout) -> Result<Response> {
|
||||||
|
Ok(login_page(hx_target, layout, LoginFormProps::default()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
mut auth: AuthContext,
|
mut auth: AuthContext,
|
||||||
|
hx_target: Option<TypedHeader<HXTarget>>,
|
||||||
|
layout: Layout,
|
||||||
Form(login): Form<Login>,
|
Form(login): Form<Login>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let user: User = match User::get_by_email(&pool, login.email.clone()).await {
|
let user: User = match User::get_by_email(&pool, login.email.clone()).await {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Error::NotFoundString(_, _) = err {
|
if let Error::NotFoundString(_, _) = err {
|
||||||
// Error::BadRequest("invalid email or password")
|
return Ok(login_page(
|
||||||
return Ok(login_form(LoginFormProps {
|
hx_target,
|
||||||
|
layout,
|
||||||
|
LoginFormProps {
|
||||||
email: Some(login.email),
|
email: Some(login.email),
|
||||||
general_error: Some("invalid email or password".to_string()),
|
general_error: Some("invalid email or password".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
},
|
||||||
.into_response());
|
));
|
||||||
} else {
|
} else {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
@ -61,13 +74,15 @@ pub async fn post(
|
|||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
// return Err(Error::BadRequest("invalid email or password"));
|
return Ok(login_page(
|
||||||
return Ok(login_form(LoginFormProps {
|
hx_target,
|
||||||
|
layout,
|
||||||
|
LoginFormProps {
|
||||||
email: Some(login.email),
|
email: Some(login.email),
|
||||||
general_error: Some("invalid email or password".to_string()),
|
general_error: Some("invalid email or password".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
},
|
||||||
.into_response());
|
));
|
||||||
}
|
}
|
||||||
auth.login(&user)
|
auth.login(&user)
|
||||||
.await
|
.await
|
||||||
|
@ -7,7 +7,7 @@ use serde_with::{serde_as, NoneAsEmptyString};
|
|||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::htmx::{HXBoosted, HXRedirect};
|
use crate::htmx::{HXTarget, HXRedirect};
|
||||||
use crate::models::user::{AuthContext, CreateUser, User};
|
use crate::models::user::{AuthContext, CreateUser, User};
|
||||||
use crate::partials::layout::Layout;
|
use crate::partials::layout::Layout;
|
||||||
use crate::partials::register_form::{register_form, RegisterFormProps};
|
use crate::partials::register_form::{register_form, RegisterFormProps};
|
||||||
@ -22,10 +22,10 @@ pub struct Register {
|
|||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(hx_boosted: Option<TypedHeader<HXBoosted>>, layout: Layout) -> Result<Response> {
|
pub async fn get(hx_target: Option<TypedHeader<HXTarget>>, layout: Layout) -> Result<Response> {
|
||||||
Ok(layout
|
Ok(layout
|
||||||
.with_subtitle("register")
|
.with_subtitle("register")
|
||||||
.boosted(hx_boosted)
|
.targeted(hx_target)
|
||||||
.render(html! {
|
.render(html! {
|
||||||
div class="center-horizontal" {
|
div class="center-horizontal" {
|
||||||
header class="center-text" {
|
header class="center-text" {
|
||||||
|
51
src/htmx.rs
51
src/htmx.rs
@ -6,7 +6,9 @@ use http::StatusCode;
|
|||||||
#[allow(clippy::declare_interior_mutable_const)]
|
#[allow(clippy::declare_interior_mutable_const)]
|
||||||
pub const HX_LOCATION: HeaderName = HeaderName::from_static("hx-location");
|
pub const HX_LOCATION: HeaderName = HeaderName::from_static("hx-location");
|
||||||
#[allow(clippy::declare_interior_mutable_const)]
|
#[allow(clippy::declare_interior_mutable_const)]
|
||||||
pub const HX_BOOSTED: HeaderName = HeaderName::from_static("hx-boosted");
|
pub const HX_REQUEST: HeaderName = HeaderName::from_static("hx-request");
|
||||||
|
#[allow(clippy::declare_interior_mutable_const)]
|
||||||
|
pub const HX_TARGET: HeaderName = HeaderName::from_static("hx-target");
|
||||||
|
|
||||||
/// Sets the HX-Location header so that HTMX redirects to the given URI. Unlike
|
/// Sets the HX-Location header so that HTMX redirects to the given URI. Unlike
|
||||||
/// axum::response::Redirect this does not return a 300-level status code (instead, a 200 status
|
/// axum::response::Redirect this does not return a 300-level status code (instead, a 200 status
|
||||||
@ -35,18 +37,18 @@ impl IntoResponse for HXRedirect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HXBoosted(bool);
|
pub struct HXRequest(bool);
|
||||||
|
|
||||||
impl HXBoosted {
|
impl HXRequest {
|
||||||
pub fn is_boosted(&self) -> bool {
|
pub fn is_true(&self) -> bool {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Header for HXBoosted {
|
impl Header for HXRequest {
|
||||||
fn name() -> &'static HeaderName {
|
fn name() -> &'static HeaderName {
|
||||||
static HX_BOOSTED_STATIC: HeaderName = HX_BOOSTED;
|
static HX_REQUEST_STATIC: HeaderName = HX_REQUEST;
|
||||||
&HX_BOOSTED_STATIC
|
&HX_REQUEST_STATIC
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
|
fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
|
||||||
@ -58,9 +60,9 @@ impl Header for HXBoosted {
|
|||||||
.ok_or_else(headers::Error::invalid)?;
|
.ok_or_else(headers::Error::invalid)?;
|
||||||
|
|
||||||
if value == "true" {
|
if value == "true" {
|
||||||
Ok(HXBoosted(true))
|
Ok(HXRequest(true))
|
||||||
} else if value == "false" {
|
} else if value == "false" {
|
||||||
Ok(HXBoosted(false))
|
Ok(HXRequest(false))
|
||||||
} else {
|
} else {
|
||||||
Err(headers::Error::invalid())
|
Err(headers::Error::invalid())
|
||||||
}
|
}
|
||||||
@ -81,3 +83,34 @@ impl Header for HXBoosted {
|
|||||||
values.extend(std::iter::once(value));
|
values.extend(std::iter::once(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HXTarget {
|
||||||
|
pub target: HeaderValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header for HXTarget {
|
||||||
|
fn name() -> &'static HeaderName {
|
||||||
|
static HX_TARGET_STATIC: HeaderName = HX_TARGET;
|
||||||
|
&HX_TARGET_STATIC
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'i HeaderValue>,
|
||||||
|
{
|
||||||
|
let value = values
|
||||||
|
.next()
|
||||||
|
.ok_or_else(headers::Error::invalid)?;
|
||||||
|
|
||||||
|
Ok(HXTarget{ target: value.clone() })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode<E>(&self, values: &mut E)
|
||||||
|
where
|
||||||
|
E: Extend<HeaderValue>,
|
||||||
|
{
|
||||||
|
|
||||||
|
values.extend(std::iter::once(self.target.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,13 +11,16 @@ use axum::{
|
|||||||
TypedHeader,
|
TypedHeader,
|
||||||
};
|
};
|
||||||
use axum_login::{extractors::AuthContext, SqlxStore};
|
use axum_login::{extractors::AuthContext, SqlxStore};
|
||||||
|
use headers::HeaderValue;
|
||||||
use maud::{html, Markup, DOCTYPE};
|
use maud::{html, Markup, DOCTYPE};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::models::user::User;
|
use crate::models::user::User;
|
||||||
use crate::{config::Config, partials::footer::footer};
|
use crate::config::Config;
|
||||||
use crate::{htmx::HXBoosted, partials::header::header};
|
use crate::htmx::HXTarget;
|
||||||
|
use crate::partials::header::header;
|
||||||
|
use crate::partials::footer::footer;
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
use crate::{CSS_MANIFEST, JS_MANIFEST};
|
use crate::{CSS_MANIFEST, JS_MANIFEST};
|
||||||
|
|
||||||
@ -26,7 +29,7 @@ pub struct Layout {
|
|||||||
pub title: String,
|
pub title: String,
|
||||||
pub subtitle: Option<String>,
|
pub subtitle: Option<String>,
|
||||||
pub user: Option<User>,
|
pub user: Option<User>,
|
||||||
pub boosted: bool,
|
pub main_content_targeted: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -118,17 +121,16 @@ impl Layout {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the given HX-Boosted header is present and "true", makes this Layout boosted.
|
/// If the given HX-Target is present and equal to "main-content", then this function will make
|
||||||
///
|
/// this Layout skip rendering the layout and only render the template with a hx-swap-oob
|
||||||
/// A boosted layout will skip rendering the layout and only render the template with a
|
/// <title> element to update the document title.
|
||||||
/// hx-swap-oob <title> element to update the document title.
|
|
||||||
///
|
///
|
||||||
/// Links and forms that are boosted with the hx-boost attribute are only updating a portion of
|
/// Links and forms that are boosted with the hx-boost attribute are only updating a portion of
|
||||||
/// the page inside the layout, so there is no need to render and send the layout again.
|
/// the page inside the layout, so there is no need to render and send the layout again.
|
||||||
pub fn boosted(mut self, hx_boosted: Option<TypedHeader<HXBoosted>>) -> Self {
|
pub fn targeted(mut self, hx_target: Option<TypedHeader<HXTarget>>) -> Self {
|
||||||
if let Some(hx_boosted) = hx_boosted {
|
if let Some(hx_target) = hx_target {
|
||||||
if hx_boosted.is_boosted() {
|
if hx_target.target == HeaderValue::from_static("main-content") {
|
||||||
self.boosted = true;
|
self.main_content_targeted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
@ -143,7 +145,7 @@ impl Layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(self, template: Markup) -> Response {
|
pub fn render(self, template: Markup) -> Response {
|
||||||
if self.boosted {
|
if self.main_content_targeted {
|
||||||
html! {
|
html! {
|
||||||
title hx-swap-oob="true" { (self.full_title()) }
|
title hx-swap-oob="true" { (self.full_title()) }
|
||||||
(template)
|
(template)
|
||||||
|
@ -16,7 +16,7 @@ pub fn login_form(props: LoginFormProps) -> Markup {
|
|||||||
general_error,
|
general_error,
|
||||||
} = props;
|
} = props;
|
||||||
html! {
|
html! {
|
||||||
form hx-post="/login" hx-swap="outerHTML" class="auth-form-grid" {
|
form action="/login" method="post" class="auth-form-grid" {
|
||||||
label for="email" { "Email" }
|
label for="email" { "Email" }
|
||||||
input type="email" name="email" id="email" placeholder="Email" value=(email.unwrap_or_default()) required;
|
input type="email" name="email" id="email" placeholder="Email" value=(email.unwrap_or_default()) required;
|
||||||
@if let Some(email_error) = email_error {
|
@if let Some(email_error) = email_error {
|
||||||
|
Loading…
Reference in New Issue
Block a user