diff --git a/extensions/pagetop-admin/src/component/admin_frame.rs b/extensions/pagetop-admin/src/component/admin_frame.rs index 690d9074..0be5c5d8 100644 --- a/extensions/pagetop-admin/src/component/admin_frame.rs +++ b/extensions/pagetop-admin/src/component/admin_frame.rs @@ -53,14 +53,14 @@ impl Component for AdminFrame { Ok(html! { (breadcrumbs) - header class="admin-header" { - h1 class="admin-page-title" { (self.title().using(cx)) } + header.admin-header { + h1.admin-page-title { (self.title().using(cx)) } (local_acts) } @if !local_tasks.0.is_empty() { - nav class="admin-local-tasks" { (local_tasks) } + nav.admin-local-tasks { (local_tasks) } } - div class="admin-content" { + div.admin-content { (body) } }) @@ -117,7 +117,7 @@ fn render_local_tasks(cx: &Context, current_path: &str) -> Markup { return html! {}; } html! { - ul class="admin-tasks-list" { + ul.admin-tasks-list { @for task in tasks { @let is_active = current_path == task.path; li class=(if is_active { "admin-task admin-task-active" } else { "admin-task" }) { @@ -137,12 +137,10 @@ fn render_local_actions(cx: &Context, current_path: &str) -> Markup { return html! {}; } html! { - ul class="admin-actions-list" { + ul.admin-actions-list { @for action in actions { - li class="admin-action" { - a class="admin-action-link" href=(cx.route(action.url.as_str())) { - (action.title.using(cx)) - } + li.admin-action { + a.admin-action-link href=(cx.route(action.url.as_str())) { (action.title.using(cx)) } } } } diff --git a/extensions/pagetop-admin/src/component/config_form.rs b/extensions/pagetop-admin/src/component/config_form.rs index 34f685b4..d8ea0da0 100644 --- a/extensions/pagetop-admin/src/component/config_form.rs +++ b/extensions/pagetop-admin/src/component/config_form.rs @@ -98,20 +98,20 @@ impl Component for ConfigForm { Ok(html! { @if *self.saved() { - div class="admin-message admin-message-ok" { (&saved_msg) } + div.admin-message."admin-message-ok" { (&saved_msg) } } @if *self.error() { - div class="admin-message admin-message-error" { (&error_msg) } + div.admin-message."admin-message-error" { (&error_msg) } } - form class="admin-config-form" method="post" action=(action) { + form.admin-config-form method="post" action=(action) { @for (i, field) in schema.fields().iter().enumerate() { @let pf = &prepared[i]; - div class="admin-form-field" { - label class="admin-form-label" for=(field.name()) { (field.label()) } + div.admin-form-field { + label.admin-form-label for=(field.name()) { (field.label()) } @match field.field_type() { SettingFieldType::Text { max_length } => { @if let Some(max) = max_length { - input class="admin-form-input" + input.admin-form-input type="text" id=(field.name()) name=(field.name()) @@ -119,7 +119,7 @@ impl Component for ConfigForm { maxlength=(max) required[*field.required()]; } @else { - input class="admin-form-input" + input.admin-form-input type="text" id=(field.name()) name=(field.name()) @@ -129,7 +129,7 @@ impl Component for ConfigForm { } SettingFieldType::Number { min, max } => { @if let (Some(lo), Some(hi)) = (min, max) { - input class="admin-form-input" + input.admin-form-input type="number" id=(field.name()) name=(field.name()) @@ -138,7 +138,7 @@ impl Component for ConfigForm { max=(hi) required[*field.required()]; } @else if let Some(lo) = min { - input class="admin-form-input" + input.admin-form-input type="number" id=(field.name()) name=(field.name()) @@ -146,7 +146,7 @@ impl Component for ConfigForm { min=(lo) required[*field.required()]; } @else if let Some(hi) = max { - input class="admin-form-input" + input.admin-form-input type="number" id=(field.name()) name=(field.name()) @@ -154,7 +154,7 @@ impl Component for ConfigForm { max=(hi) required[*field.required()]; } @else { - input class="admin-form-input" + input.admin-form-input type="number" id=(field.name()) name=(field.name()) @@ -163,7 +163,7 @@ impl Component for ConfigForm { } } SettingFieldType::Boolean => { - input class="admin-form-checkbox" + input.admin-form-checkbox type="checkbox" id=(field.name()) name=(field.name()) @@ -171,7 +171,7 @@ impl Component for ConfigForm { checked[pf.checked]; } SettingFieldType::Select { options } => { - select class="admin-form-select" + select.admin-form-select id=(field.name()) name=(field.name()) required[*field.required()] @@ -187,12 +187,12 @@ impl Component for ConfigForm { } } @if let Some(help) = field.help_text() { - small class="admin-form-help" { (help) } + small.admin-form-help { (help) } } } } - div class="admin-form-actions" { - button class="admin-btn admin-btn-primary" type="submit" { + div.admin-form-actions { + button.admin-btn."admin-btn-primary" type="submit" { (&save_label) } } diff --git a/extensions/pagetop-admin/src/handlers.rs b/extensions/pagetop-admin/src/handlers.rs index c9a3d979..8a512d08 100644 --- a/extensions/pagetop-admin/src/handlers.rs +++ b/extensions/pagetop-admin/src/handlers.rs @@ -4,25 +4,27 @@ use pagetop::prelude::*; use crate::LOCALES_ADMIN; use crate::component::{AdminFrame, ConfigForm}; -use crate::registry::{self, AdminPageKind, AdminSection}; +use crate::registry::{self, AdminPageKind, AdminPermission, AdminSection}; use crate::settings; // **< Dashboard >********************************************************************************** /// GET /admin - Dashboard de administración: lista todas las secciones disponibles. -/// -/// Devuelve [`ErrorPage::AccessDenied`] si el usuario actual no tiene acceso a ninguna página. pub async fn dashboard(request: HttpRequest) -> Result { + let reg = registry::global(); let cx = Context::new(request.clone()); - let sections = registry::global().ordered_sections(); + let sections = reg.ordered_sections(); let title = Lc::t("dashboard-title", &LOCALES_ADMIN); - let content = render_sections(&cx, §ions) - .ok_or_else(|| ErrorPage::AccessDenied(Some(request.clone())))?; + let content = render_sections(&cx, §ions); Page::admin(request) .with_title(title.clone()) - .with_child(AdminFrame::new().with_title(title).with_child(content)) + .with_child( + AdminFrame::new() + .with_title(title) + .with_child(Html::with(move |_| content.clone())), + ) .render() .await } @@ -35,9 +37,6 @@ pub async fn dashboard(request: HttpRequest) -> Result { /// con ninguna [`AdminPage`](crate::registry::AdminPage) registrada explícitamente por una /// extensión; si una extensión reclama esa ruta con su propia página, esa página gana y este /// handler nunca llega a montarse para ella. -/// -/// Devuelve [`ErrorPage::AccessDenied`] si el usuario actual no tiene acceso a ninguna de sus -/// páginas. pub async fn section_page(request: HttpRequest) -> Result { let path = request.path().to_owned(); let reg = registry::global(); @@ -47,14 +46,22 @@ pub async fn section_page(request: HttpRequest) -> Result { .find(|s| s.path == path) .ok_or_else(|| ErrorPage::NotFound(Some(request.clone())))?; + require_permission( + &request, + section.permission.unwrap_or(&AdminPermission::Access), + )?; + let cx = Context::new(request.clone()); let title = section.title.clone(); - let content = render_sections(&cx, &[section]) - .ok_or_else(|| ErrorPage::AccessDenied(Some(request.clone())))?; + let content = render_sections(&cx, &[section]); Page::admin(request) .with_title(title.clone()) - .with_child(AdminFrame::new().with_title(title).with_child(content)) + .with_child( + AdminFrame::new() + .with_title(title) + .with_child(Html::with(move |_| content.clone())), + ) .render() .await } @@ -62,56 +69,41 @@ pub async fn section_page(request: HttpRequest) -> Result { // **< Render compartido >************************************************************************** // Rejilla de secciones con sus páginas (título + descripción). La usan tanto `dashboard()` (todas -// las secciones) como `section_page()` (una sola), filtrando siempre por permiso del usuario -// actual. Devuelve `None` si no queda ninguna sección visible con páginas accesibles. -fn render_sections(cx: &Context, candidates: &[&'static AdminSection]) -> Option { +// las secciones) como `section_page()` (una sola), filtrando siempre por permiso del usuario actual. +fn render_sections(cx: &Context, sections: &[&AdminSection]) -> Markup { let reg = registry::global(); - let sections_with_pages: Vec<_> = candidates - .iter() - .copied() - .filter(|section| section.is_visible(cx)) - .map(|section| { - let pages: Vec<_> = reg - .pages_for_section(§ion.key) - .into_iter() - .filter(|p| p.is_accessible(cx)) - .collect(); - (section, pages) - }) - .filter(|(_, pages)| !pages.is_empty()) - .collect(); - if sections_with_pages.is_empty() { - return None; - } - let mut sections = Container::new().with_prop(PropsOp::add_classes("admin-dashboard-sections")); - for (section, pages) in sections_with_pages { - sections = sections.with_child( - Container::new() - .with_prop(PropsOp::add_classes("admin-dashboard-section")) - .with_child(Html::with(move |cx| { - html! { - h2 class="admin-dashboard-section-title" { - a href=(cx.route(section.path.as_str())) { (section.title.using(cx)) } - } - ul class="admin-dashboard-section-links" { - @for page in &pages { - li class="admin-dashboard-link" { - a href=(cx.route(page.path.as_str())) { - (page.title.using(cx)) - } - @if let Some(description) = &page.description { - span class="admin-dashboard-link-desc" { - " - " (description.using(cx)) + html! { + div.admin-dashboard-sections { + @for section in sections { + @if section.is_visible(cx) { + @let pages: Vec<_> = reg + .pages_for_section(§ion.key) + .into_iter() + .filter(|p| p.is_accessible(cx)) + .collect(); + @if !pages.is_empty() { + div.admin-dashboard-section { + h2.admin-dashboard-section-title { + a href=(cx.route(section.path.as_str())) { (section.title.using(cx)) } + } + ul.admin-dashboard-section-links { + @for page in pages { + li.admin-dashboard-link { + a href=(cx.route(page.path.as_str())) { (page.title.using(cx)) } + @if let Some(description) = &page.description { + span.admin-dashboard-link-desc { + " - " (description.using(cx)) + } } } } } } } - })), - ); + } + } + } } - Some(sections) } // **< Config form - GET >************************************************************************** diff --git a/extensions/pagetop-htmx/README.md b/extensions/pagetop-htmx/README.md index 6fbc8144..47382b51 100644 --- a/extensions/pagetop-htmx/README.md +++ b/extensions/pagetop-htmx/README.md @@ -58,7 +58,7 @@ async fn homepage(request: HttpRequest) -> Result { button hx-get="/api/hello" hx-target="#result" { "Say hello" } - div id="result" {} + div #result {} })) .render().await } @@ -79,7 +79,7 @@ async fn homepage(request: HttpRequest) -> Result { Page::new(request) .with_child(Html::with(move |cx| html! { button (props.unpack(cx)) { "Say hello" } - div id="result" {} + div #result {} })) .render().await } diff --git a/extensions/pagetop-htmx/src/lib.rs b/extensions/pagetop-htmx/src/lib.rs index e7d6acf9..9404b49b 100644 --- a/extensions/pagetop-htmx/src/lib.rs +++ b/extensions/pagetop-htmx/src/lib.rs @@ -59,7 +59,7 @@ async fn homepage(request: HttpRequest) -> Result { button hx-get="/api/hello" hx-target="#result" { "Say hello" } - div id="result" {} + div #result {} })) .render().await } @@ -80,7 +80,7 @@ async fn homepage(request: HttpRequest) -> Result { Page::new(request) .with_child(Html::with(move |cx| html! { button (props.unpack(cx)) { "Say hello" } - div id="result" {} + div #result {} })) .render().await } diff --git a/extensions/pagetop-htmx/src/response.rs b/extensions/pagetop-htmx/src/response.rs index c44505e0..cf512633 100644 --- a/extensions/pagetop-htmx/src/response.rs +++ b/extensions/pagetop-htmx/src/response.rs @@ -21,7 +21,7 @@ use crate::hx; /// use pagetop_htmx::prelude::*; /// /// async fn add_item() -> impl IntoResponse { -/// let new_item = html! { li id="item-42" { "New item" } }; +/// let new_item = html! { li #item-42 { "New item" } }; /// /// HtmxResponse::new(new_item) /// .retarget("#list") @@ -123,7 +123,7 @@ impl HtmxResponse { /// /// # fn build_response(table: Markup, roles_count: i64) -> HtmxResponse { /// let counter = html! { - /// span id="roles-count" hx-swap-oob="true" { (roles_count) } + /// span #roles-count hx-swap-oob="true" { (roles_count) } /// }; /// HtmxResponse::new(table).oob(counter) /// # } diff --git a/extensions/pagetop-htmx/tests/response.rs b/extensions/pagetop-htmx/tests/response.rs index adfdb9f5..1f958139 100644 --- a/extensions/pagetop-htmx/tests/response.rs +++ b/extensions/pagetop-htmx/tests/response.rs @@ -15,7 +15,7 @@ fn header<'a>(response: &'a web::Response, name: &str) -> Option<&'a str> { #[pagetop::test] async fn new_renders_the_given_markup_with_an_html_content_type() { - let response = HtmxResponse::new(html! { li id="item-42" { "New item" } }).into_response(); + let response = HtmxResponse::new(html! { li #item-42 { "New item" } }).into_response(); assert_eq!( header(&response, "content-type"), @@ -41,8 +41,8 @@ async fn empty_has_no_body_but_keeps_the_html_content_type() { #[pagetop::test] async fn oob_appends_markup_after_the_main_body() { - let response = HtmxResponse::new(html! { li id="item-42" { "New item" } }) - .oob(html! { span id="item-count" hx-swap-oob="true" { "1" } }) + let response = HtmxResponse::new(html! { li #item-42 { "New item" } }) + .oob(html! { span #item-count hx-swap-oob="true" { "1" } }) .into_response(); let body = web::test::read_body_text(response).await; @@ -58,8 +58,8 @@ async fn oob_appends_markup_after_the_main_body() { #[pagetop::test] async fn oob_can_be_called_several_times_to_accumulate_fragments() { let response = HtmxResponse::new(html! { p { "Main" } }) - .oob(html! { span id="a" hx-swap-oob="true" { "A" } }) - .oob(html! { span id="b" hx-swap-oob="true" { "B" } }) + .oob(html! { span #a hx-swap-oob="true" { "A" } }) + .oob(html! { span #b hx-swap-oob="true" { "B" } }) .into_response(); let body = web::test::read_body_text(response).await; @@ -76,7 +76,7 @@ async fn oob_can_be_called_several_times_to_accumulate_fragments() { #[pagetop::test] async fn oob_works_from_an_empty_response() { let response = HtmxResponse::empty() - .oob(html! { span id="item-count" hx-swap-oob="true" { "0" } }) + .oob(html! { span #item-count hx-swap-oob="true" { "0" } }) .into_response(); let body = web::test::read_body_text(response).await; diff --git a/extensions/pagetop-user/src/component.rs b/extensions/pagetop-user/src/component.rs index 179ac779..e52e00fa 100644 --- a/extensions/pagetop-user/src/component.rs +++ b/extensions/pagetop-user/src/component.rs @@ -22,7 +22,7 @@ use pagetop::prelude::*; // formularios de autenticación y por los de administración. pub(crate) fn error_banner(error: Option) -> Html { Html::with(move |cx| match &error { - Some(e) => html! { div class="user-form-error" role="alert" { (e.clone().using(cx)) } }, + Some(e) => html! { div.user-form-error role="alert" { (e.clone().using(cx)) } }, None => html! {}, }) } diff --git a/extensions/pagetop-user/src/component/admin/role_table.rs b/extensions/pagetop-user/src/component/admin/role_table.rs index 1516f975..231273d8 100644 --- a/extensions/pagetop-user/src/component/admin/role_table.rs +++ b/extensions/pagetop-user/src/component/admin/role_table.rs @@ -111,13 +111,13 @@ impl Component for RoleTable { Ok(html! { div (self.props().unpack(cx)) { - div class="user-admin-actions" { + div.user-admin-actions { a href=(new_href) { (Lc::t("btn-create-role", &LOCALES_USER).using(cx)) } } @if let Some(message) = self.message() { - div class="user-form-error" role="alert" { (message.clone().using(cx)) } + div.user-form-error role="alert" { (message.clone().using(cx)) } } (table.render(cx).await) (pager) diff --git a/extensions/pagetop-user/src/component/admin/user_table.rs b/extensions/pagetop-user/src/component/admin/user_table.rs index 97664a13..603dcf98 100644 --- a/extensions/pagetop-user/src/component/admin/user_table.rs +++ b/extensions/pagetop-user/src/component/admin/user_table.rs @@ -87,7 +87,7 @@ impl Component for UserTable { Ok(html! { div (self.props().unpack(cx)) { - div class="user-admin-actions" { + div.user-admin-actions { a href=(new_href) { (Lc::t("btn-create-user", &LOCALES_USER).using(cx)) } diff --git a/extensions/pagetop-user/src/component/login_form.rs b/extensions/pagetop-user/src/component/login_form.rs index ab5c8381..ec582d0b 100644 --- a/extensions/pagetop-user/src/component/login_form.rs +++ b/extensions/pagetop-user/src/component/login_form.rs @@ -69,8 +69,8 @@ impl Component for LoginForm { let form = form.render(cx).await; Ok(html! { - div class="user-login-page" { - div class="user-login-card" { + div.user-login-page { + div.user-login-card { (form) } } @@ -82,13 +82,13 @@ fn links(allow_registration: bool) -> Html { Html::with(move |cx| { html! { @if allow_registration { - p class="user-register-link" { + p.user-register-link { a href=(cx.route(REGISTER_PATH)) { (Lc::t("link-register", &LOCALES_USER).using(cx)) } } } - p class="user-reset-link" { + p.user-reset-link { a href=(cx.route(PASSWORD_RESET_PATH)) { (Lc::t("link-forgot-password", &LOCALES_USER).using(cx)) } diff --git a/extensions/pagetop-user/src/component/user_block.rs b/extensions/pagetop-user/src/component/user_block.rs index 62945e52..d31ccac2 100644 --- a/extensions/pagetop-user/src/component/user_block.rs +++ b/extensions/pagetop-user/src/component/user_block.rs @@ -19,10 +19,10 @@ impl Component for UserBlock { Ok(if user.is_authenticated() { let display = user.display_name().unwrap_or("?"); html! { - nav class="user-block" { - span class="user-name" { (display) } + nav.user-block { + span.user-name { (display) } " · " - form class="user-logout-inline" method="post" action=(cx.route(LOGOUT_PATH)) { + form.user-logout-inline method="post" action=(cx.route(LOGOUT_PATH)) { button type="submit" { (Lc::t("btn-logout", &LOCALES_USER).using(cx)) } @@ -31,7 +31,7 @@ impl Component for UserBlock { } } else { html! { - nav class="user-block" { + nav.user-block { a href=(cx.route(LOGIN_PATH)) { (Lc::t("btn-login", &LOCALES_USER).using(cx)) } diff --git a/extensions/pagetop-user/src/handlers/account.rs b/extensions/pagetop-user/src/handlers/account.rs index f89f7fd8..dac6f536 100644 --- a/extensions/pagetop-user/src/handlers/account.rs +++ b/extensions/pagetop-user/src/handlers/account.rs @@ -119,7 +119,7 @@ async fn profile_roles(roles: &[role::Model], cx: &mut Context) -> Block { @if items.is_empty() { "-" } @else { - ul class="user-profile-roles" { + ul.user-profile-roles { @for (label, system_badge) in &items { li { (label.as_str()) diff --git a/extensions/pagetop-user/src/handlers/admin.rs b/extensions/pagetop-user/src/handlers/admin.rs index 0ee29370..e4aaba45 100644 --- a/extensions/pagetop-user/src/handlers/admin.rs +++ b/extensions/pagetop-user/src/handlers/admin.rs @@ -51,7 +51,7 @@ pub(crate) fn back_link(href: impl Into) -> Html { let href = href.into(); Html::with(move |cx| { html! { - p class="user-admin-back-link" { + p.user-admin-back-link { a href=(href.clone()) { (Lc::t("link-back-to-list", &LOCALES_USER).using(cx)) } } } diff --git a/extensions/pagetop-user/src/handlers/admin/permissions.rs b/extensions/pagetop-user/src/handlers/admin/permissions.rs index 6f451114..62d6e1df 100644 --- a/extensions/pagetop-user/src/handlers/admin/permissions.rs +++ b/extensions/pagetop-user/src/handlers/admin/permissions.rs @@ -26,12 +26,12 @@ pub(crate) async fn list_get(request: HttpRequest) -> Result Vec { .with_title(group_label.clone()) .with_child(Html::with(move |cx| { html! { - ul class="user-admin-permission-list" { + ul.user-admin-permission-list { @for (_key, label, granted) in &perms { @if *granted { - li class="user-admin-permission-granted" { (label.using(cx)) } + li.user-admin-permission-granted { (label.using(cx)) } } @else { - li class="user-admin-permission-missing" { (label.using(cx)) } + li.user-admin-permission-missing { (label.using(cx)) } } } } diff --git a/extensions/pagetop-user/src/handlers/admin/users.rs b/extensions/pagetop-user/src/handlers/admin/users.rs index 54bcb7fb..c757fc35 100644 --- a/extensions/pagetop-user/src/handlers/admin/users.rs +++ b/extensions/pagetop-user/src/handlers/admin/users.rs @@ -92,7 +92,7 @@ fn search_bar(current_query: Option) -> Html { let value = current_query.unwrap_or_default(); Html::with(move |cx| { html! { - div class="user-admin-search" { + div.user-admin-search { input type="search" id="user-admin-search-input" @@ -588,7 +588,7 @@ async fn user_view_roles(roles: &[role::Model], cx: &mut Context) -> Block { @if items.is_empty() { "-" } @else { - table class="user-admin-table" { + table.user-admin-table { tbody { @for (id, machine_name, label, system_badge) in &items { @let href = cx.route(role_path(*id, "view")).to_string(); diff --git a/src/base/component/breadcrumb/component.rs b/src/base/component/breadcrumb/component.rs index cf598a2a..45a65882 100644 --- a/src/base/component/breadcrumb/component.rs +++ b/src/base/component/breadcrumb/component.rs @@ -57,7 +57,7 @@ impl Component for Breadcrumb { Ok(html! { nav (self.props().unpack(cx)) aria-label=[Lc::l("breadcrumb_label").lookup(cx)] { - ol class="breadcrumb" { + ol.breadcrumb { @for crumb in self.crumbs() { (crumb.render_crumb(cx)) } diff --git a/src/base/component/layout/template.rs b/src/base/component/layout/template.rs index a3c9bfeb..3f1b85f1 100644 --- a/src/base/component/layout/template.rs +++ b/src/base/component/layout/template.rs @@ -77,7 +77,7 @@ impl Component for Template { } Ok(html! { - div class="wrapper" { + div.wrapper { (body) } }) diff --git a/src/base/component/pager.rs b/src/base/component/pager.rs index 16afdbde..f8eacb00 100644 --- a/src/base/component/pager.rs +++ b/src/base/component/pager.rs @@ -240,7 +240,7 @@ impl Component for Pager { @let items_per_page = self.items_per_page().max(1); @let first = (page - 1) * items_per_page + 1; @let last = (page * items_per_page).min(self.total_items()); - span class="pager-summary" { + span.pager-summary { (Lc::l("pager_summary") .with_arg("first", first.to_string()) .with_arg("last", last.to_string()) @@ -248,16 +248,14 @@ impl Component for Pager { .using(cx)) } } - ul class="pagination" { + ul.pagination { @if show_prev_next { li.page-item.page-previous.disabled[first_disabled] { - a class="page-link" + a.page-link href=[(!first_disabled).then(|| Self::page_route(&route, page - 1))] aria-disabled=[first_disabled.then_some("true")] aria-label=[Lc::l("pager_previous_aria_label").lookup(cx)] { - span class="page-link-icon" { - (Lc::l("pager_previous_label").using(cx)) - } + span.page-link-icon { (Lc::l("pager_previous_label").using(cx)) } } } } @@ -266,7 +264,7 @@ impl Component for Pager { PageItem::Number(n) => { @let href = Self::page_route(&route, n); li.page-item.active[n == page] { - a class="page-link" + a.page-link href=(href) aria-current=[(n == page).then_some("page")] { (n.to_string()) @@ -274,21 +272,19 @@ impl Component for Pager { } } PageItem::Ellipsis => { - li class="page-item page-ellipsis disabled" aria-hidden="true" { - span class="page-link" { "…" } + li.page-item.page-ellipsis.disabled aria-hidden="true" { + span.page-link { "…" } } } } } @if show_prev_next { li.page-item.page-next.disabled[last_disabled] { - a class="page-link" + a.page-link href=[(!last_disabled).then(|| Self::page_route(&route, page + 1))] aria-disabled=[last_disabled.then_some("true")] aria-label=[Lc::l("pager_next_aria_label").lookup(cx)] { - span class="page-link-icon" { - (Lc::l("pager_next_label").using(cx)) - } + span.page-link-icon { (Lc::l("pager_next_label").using(cx)) } } } } diff --git a/src/base/component/table/component.rs b/src/base/component/table/component.rs index 951e63be..72b12ffe 100644 --- a/src/base/component/table/component.rs +++ b/src/base/component/table/component.rs @@ -80,7 +80,7 @@ impl Component for Table { let colspan = self.columns().len().max(1).to_string(); Ok(html! { - div class="table-responsive" { + div.table-responsive { table (self.props().unpack(cx)) { @if !self.columns().is_empty() { thead { @@ -108,7 +108,7 @@ impl Component for Table { { tbody { tr { - td class="table-empty" colspan=(colspan) { (empty) } + td.table-empty colspan=(colspan) { (empty) } } } } diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 63c9101d..9792877a 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -312,8 +312,8 @@ pub trait Theme: Extension + Send + Sync { /// cx: &mut Context, /// ) -> Option> { /// render_component!(component, { - /// Button => |btn| Ok(html! { button class="btn btn-primary" { (btn.label()) } }), - /// Heading => |h| Ok(html! { h2 class="display-4" { (h.text()) } }), + /// Button => |btn| Ok(html! { button.btn.btn-primary { (btn.label()) } }), + /// Heading => |h| Ok(html! { h2.display-4 { (h.text()) } }), /// }) /// } /// ``` @@ -487,13 +487,13 @@ macro_rules! setup_component { /// cx: &mut Context, /// ) -> Option> { /// render_component!(component, { -/// Button => |btn| { Ok(html! { button class="btn btn-primary" { (btn.label()) } }) }, +/// Button => |btn| { Ok(html! { button.btn.btn-primary { (btn.label()) } }) }, /// Heading => |h| self.render_heading(h, cx), /// }) /// } /// /// fn render_heading(&self, h: &Heading, cx: &mut Context) -> Result { -/// Ok(html! { h2 class="display-4" { (h.text()) } }) +/// Ok(html! { h2.display-4 { (h.text()) } }) /// } /// ``` ///