//! Barra core — modelo agnóstico de taskbar. //! //! Provee la lista de `Task`, los helpers de sanitización para atributos //! HTML, y `render_html` puro. El binding DOM vive en `barra-web`. /// Una tarea (cajita) en la barra. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Task { pub id: String, pub label: String, pub active: bool, } impl Task { pub fn new(id: impl Into, label: impl Into) -> Self { Self { id: id.into(), label: label.into(), active: false } } pub fn active(mut self) -> Self { self.active = true; self } } /// Renderiza un slice de tareas a markup HTML. Sanitiza IDs y escapa /// labels. La salida es la lista de `
  • ` que el host inyecta en su `
      `. pub fn render_html(tasks: &[Task]) -> String { let mut html = String::new(); for t in tasks { let id_safe = sanitize_attr(&t.id); let label_safe = escape_text(&t.label); let active_cls = if t.active { " active" } else { "" }; html.push_str(&format!( "
    • " )); } html } /// Filtra a `[a-zA-Z0-9_-]` para uso seguro en atributos HTML. pub fn sanitize_attr(s: &str) -> String { s.chars() .filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_') .collect() } /// HTML-escape de texto para insertarlo en posiciones de contenido. pub fn escape_text(s: &str) -> String { let mut out = String::with_capacity(s.len()); for c in s.chars() { match c { '&' => out.push_str("&"), '<' => out.push_str("<"), '>' => out.push_str(">"), '"' => out.push_str("""), c => out.push(c), } } out } #[cfg(test)] mod tests { use super::*; #[test] fn task_builder_defaults_inactive() { let t = Task::new("aire", "AIRE"); assert!(!t.active); assert!(Task::new("f", "F").active().active); } #[test] fn sanitize_attr_strips_unsafe() { assert_eq!(sanitize_attr("aire"), "aire"); assert_eq!(sanitize_attr("a-b_c"), "a-b_c"); assert_eq!(sanitize_attr("ai"), "aire"); assert_eq!(sanitize_attr("a\"b"), "ab"); } #[test] fn escape_text_escapes_html() { assert_eq!(escape_text("AIRE"), "AIRE"); assert_eq!(escape_text("