♻️ (minimal): Reimplementa join! y join_pair!

Usa `[&str]::concat()` de la biblioteca estándar y elimina la
dependencia de `concat-string`. Cada argumento se evalúa una sola vez y
no se consume.
This commit is contained in:
Manuel Cillero 2026-09-20 14:33:43 +02:00
parent c6cd4fcd7d
commit 8651b91ab2
19 changed files with 105 additions and 108 deletions

View file

@ -2,8 +2,8 @@
use pagetop::prelude::*;
use crate::ADMIN_USERS_PATH;
use crate::LOCALES_USER;
use crate::user_path;
use crate::component::{PasswordConfirm, error_banner};
@ -22,7 +22,7 @@ impl Component for AdminPasswordForm {
}
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
let action = format!("{ADMIN_USERS_PATH}/{}/password", self.user_id());
let action = user_path(self.user_id(), "password");
let action = self.waypoint().append_to(cx.route(action));
let mut form = Form::new()

View file

@ -4,6 +4,7 @@ use pagetop::prelude::*;
use crate::ADMIN_ROLES_PATH;
use crate::LOCALES_USER;
use crate::role_path;
use crate::component::error_banner;
@ -35,13 +36,8 @@ impl Component for RoleForm {
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
let action = match self.mode() {
RoleFormMode::New => format!("{ADMIN_ROLES_PATH}/new"),
RoleFormMode::Edit => {
format!(
"{ADMIN_ROLES_PATH}/{}/edit",
self.role_id().copied().unwrap_or_default()
)
}
RoleFormMode::New => util::join!(ADMIN_ROLES_PATH, "/new"),
RoleFormMode::Edit => role_path(self.role_id().copied().unwrap_or_default(), "edit"),
};
let action = self.waypoint().append_to(cx.route(action));

View file

@ -4,8 +4,8 @@
use pagetop::prelude::*;
use crate::ADMIN_ROLES_PATH;
use crate::LOCALES_USER;
use crate::role_path;
use crate::component::admin::PermissionGroups;
use crate::component::error_banner;
@ -26,7 +26,7 @@ impl Component for RolePermissionsForm {
}
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
let action = format!("{ADMIN_ROLES_PATH}/{}/permissions", self.role_id());
let action = role_path(self.role_id(), "permissions");
let action = self.waypoint().append_to(cx.route(action));
let mut form = Form::new()
@ -37,12 +37,12 @@ impl Component for RolePermissionsForm {
for (idx, (group_label, perms)) in self.groups().iter().enumerate() {
let mut field = form::check::Field::new()
.with_id(format!("permission-group-{idx}"))
.with_id(util::join!("permission-group-", idx.to_string()))
.with_name("permission_keys");
for (key, label, checked) in perms {
let text = label.lookup(cx).unwrap_or_default();
field = field.with_item(
form::check::Item::new(key, Lc::n(format!("{text} ({key})")))
form::check::Item::new(key, Lc::n(util::join!(text, " (", key, ")")))
.with_checked(*checked),
);
}

View file

@ -8,6 +8,7 @@ use pagetop_htmx::hx_table::sort_link;
use crate::ADMIN_ROLES_PATH;
use crate::LOCALES_USER;
use crate::role_path;
use crate::service::role_admin::{RoleListItem, RoleSortField};
#[derive(AutoDefault, Clone, Debug, Getters)]
@ -108,7 +109,7 @@ impl Component for RoleTable {
);
}
let new_href = waypoint.append_to(cx.route(format!("{ADMIN_ROLES_PATH}/new")));
let new_href = waypoint.append_to(cx.route(util::join!(ADMIN_ROLES_PATH, "/new")));
Ok(html! {
div (self.props().unpack(cx)) {
@ -212,7 +213,7 @@ fn label_cell(role: &RoleListItem, waypoint: &Waypoint) -> Html {
let id = role.id;
let waypoint = waypoint.clone();
Html::with(move |cx| {
let view_href = waypoint.append_to(cx.route(format!("{ADMIN_ROLES_PATH}/{id}/view")));
let view_href = waypoint.append_to(cx.route(role_path(id, "view")));
html! {
a href=(view_href) { (label.as_str()) }
}
@ -234,8 +235,7 @@ async fn actions_cell(
let locked = role.locked;
let waypoint = waypoint.clone();
let permissions_href =
waypoint.append_to(cx.route(format!("{ADMIN_ROLES_PATH}/{id}/permissions")));
let permissions_href = waypoint.append_to(cx.route(role_path(id, "permissions")));
// Los botones se renderizan aquí, no dentro del `Html::with()` de abajo: necesitan pasar por
// su propio ciclo de renderizado (`.render().await`) para que el tema activo los estilice
@ -253,7 +253,7 @@ async fn actions_cell(
let (edit_button, delete_button) = if locked {
(None, None)
} else {
let edit_href = waypoint.append_to(cx.route(format!("{ADMIN_ROLES_PATH}/{id}/edit")));
let edit_href = waypoint.append_to(cx.route(role_path(id, "edit")));
let edit_button = Button::anchor(Lc::t("btn-edit", &LOCALES_USER), edit_href)
.with_style(button::Style::Solid(Intent::Primary))
.with_size(button::Size::Small)
@ -263,7 +263,7 @@ async fn actions_cell(
// Viaja como query string para que, tanto si el borrado falla como si tiene éxito, la
// tabla vuelva a mostrarse en la misma página/orden en que estaba, en vez de reiniciarse.
let confirm_href = cx
.route(format!("{ADMIN_ROLES_PATH}/{id}/delete/confirm"))
.route(role_path(id, "delete/confirm"))
.alter_param("sort", sort.as_str())
.alter_param("dir", dir)
.alter_param("page", page.to_string())

View file

@ -4,6 +4,7 @@ use pagetop::prelude::*;
use crate::ADMIN_USERS_PATH;
use crate::LOCALES_USER;
use crate::user_path;
use crate::component::{PasswordConfirm, error_banner};
@ -43,13 +44,8 @@ impl Component for UserForm {
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
let action = match self.mode() {
UserFormMode::New => format!("{ADMIN_USERS_PATH}/new"),
UserFormMode::Edit => {
format!(
"{ADMIN_USERS_PATH}/{}/edit",
self.user_id().copied().unwrap_or_default()
)
}
UserFormMode::New => util::join!(ADMIN_USERS_PATH, "/new"),
UserFormMode::Edit => user_path(self.user_id().copied().unwrap_or_default(), "edit"),
};
let action = self.waypoint().append_to(cx.route(action));

View file

@ -2,8 +2,8 @@
use pagetop::prelude::*;
use crate::ADMIN_USERS_PATH;
use crate::LOCALES_USER;
use crate::user_path;
use crate::component::error_banner;
@ -25,7 +25,7 @@ impl Component for UserRolesForm {
}
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
let action = format!("{ADMIN_USERS_PATH}/{}/roles", self.user_id());
let action = user_path(self.user_id(), "roles");
let action = self.waypoint().append_to(cx.route(action));
let mut form = Form::new()

View file

@ -11,6 +11,7 @@ use crate::LOCALES_USER;
use crate::account::UserStatus;
use crate::permission::UserPermission;
use crate::service::user_admin::{UserListItem, UserSortField};
use crate::user_path;
#[derive(AutoDefault, Clone, Debug, Getters)]
pub(crate) struct UserTable {
@ -84,7 +85,7 @@ impl Component for UserTable {
);
}
let new_href = waypoint.append_to(cx.route(format!("{ADMIN_USERS_PATH}/new")));
let new_href = waypoint.append_to(cx.route(util::join!(ADMIN_USERS_PATH, "/new")));
Ok(html! {
div (self.props().unpack(cx)) {
@ -191,7 +192,7 @@ fn username_cell(user: &UserListItem, waypoint: &Waypoint) -> Html {
let id = user.id;
let waypoint = waypoint.clone();
Html::with(move |cx| {
let view_href = waypoint.append_to(cx.route(format!("{ADMIN_USERS_PATH}/{id}/view")));
let view_href = waypoint.append_to(cx.route(user_path(id, "view")));
html! {
a href=(view_href) { (username.as_str()) }
}
@ -208,7 +209,7 @@ async fn actions_cell(
cx: &mut Context,
) -> Html {
let id = user.id;
let edit_href = waypoint.append_to(cx.route(format!("{ADMIN_USERS_PATH}/{id}/edit")));
let edit_href = waypoint.append_to(cx.route(user_path(id, "edit")));
// El botón se renderiza aquí, no dentro del `Html::with()` de abajo: necesita pasar por su
// propio ciclo de renderizado (`.render().await`) para que el tema activo lo estilice igual
@ -220,7 +221,7 @@ async fn actions_cell(
.await;
let roles_button = if can_assign_roles {
let roles_href = waypoint.append_to(cx.route(format!("{ADMIN_USERS_PATH}/{id}/roles")));
let roles_href = waypoint.append_to(cx.route(user_path(id, "roles")));
Some(
Button::anchor(Lc::t("btn-manage-roles", &LOCALES_USER), roles_href)
.with_style(button::Style::Solid(Intent::Neutral))