feat: attributes on body and html

This commit is contained in:
Greg Johnston 2024-07-06 09:39:54 -04:00
parent f70702c6c4
commit 8a878eaaa4
2 changed files with 76 additions and 67 deletions

View file

@ -51,6 +51,7 @@ use futures::{Stream, StreamExt};
use leptos::{
attr::NextAttribute,
component,
html::attributes_to_html,
logging::debug_warn,
reactive_graph::owner::{provide_context, use_context},
tachys::{
@ -234,7 +235,13 @@ impl ServerMetaContextOutput {
// collect all registered meta tags
let meta_buf = self.elements.into_iter().collect::<String>();
let modified_chunk = if title_len == 0 && meta_buf.is_empty() {
// get HTML strings for `<html>` and `<body>`
let mut html_attrs = String::new();
_ = attributes_to_html(self.html, &mut html_attrs);
let mut body_attrs = String::new();
_ = attributes_to_html(self.body, &mut body_attrs);
let mut modified_chunk = if title_len == 0 && meta_buf.is_empty() {
first_chunk
} else {
let mut buf = String::with_capacity(
@ -263,6 +270,22 @@ impl ServerMetaContextOutput {
buf
};
if !html_attrs.is_empty() {
if let Some(index) = modified_chunk.find("<html") {
// Calculate the position where the new string should be inserted
let insert_pos = index + "<html".len();
modified_chunk.insert_str(insert_pos, &html_attrs);
}
}
if !body_attrs.is_empty() {
if let Some(index) = modified_chunk.find("<body") {
// Calculate the position where the new string should be inserted
let insert_pos = index + "<body".len();
modified_chunk.insert_str(insert_pos, &body_attrs);
}
}
futures::stream::once(async move { modified_chunk }).chain(stream)
}
}

View file

@ -278,37 +278,7 @@ where
buf.push('<');
buf.push_str(E::TAG);
// attributes
// `class` and `style` are created first, and pushed later
// this is because they can be filled by a mixture of values that include
// either the whole value (`class="..."` or `style="..."`) and individual
// classes and styles (`class:foo=true` or `style:height="40px"`), so they
// need to be filled during the whole attribute-creation process and then
// added
// String doesn't allocate until the first push, so this is cheap if there
// is no class or style on an element
let mut class = String::new();
let mut style = String::new();
let mut inner_html = String::new();
// inject regular attributes, and fill class and style
self.attributes
.to_html(buf, &mut class, &mut style, &mut inner_html);
if !class.is_empty() {
buf.push(' ');
buf.push_str("class=\"");
buf.push_str(&escape_attr(class.trim_start().trim_end()));
buf.push('"');
}
if !style.is_empty() {
buf.push(' ');
buf.push_str("style=\"");
buf.push_str(&escape_attr(style.trim_start().trim_end()));
buf.push('"');
}
let inner_html = attribute_to_html(self.attributes, buf);
buf.push('>');
@ -346,41 +316,7 @@ where
buf.push('<');
buf.push_str(E::TAG);
// attributes
// `class` and `style` are created first, and pushed later
// this is because they can be filled by a mixture of values that include
// either the whole value (`class="..."` or `style="..."`) and individual
// classes and styles (`class:foo=true` or `style:height="40px"`), so they
// need to be filled during the whole attribute-creation process and then
// added
// String doesn't allocate until the first push, so this is cheap if there
// is no class or style on an element
let mut class = String::new();
let mut style = String::new();
let mut inner_html = String::new();
// inject regular attributes, and fill class and style
self.attributes.to_html(
&mut buf,
&mut class,
&mut style,
&mut inner_html,
);
if !class.is_empty() {
buf.push(' ');
buf.push_str("class=\"");
buf.push_str(class.trim_start().trim_end());
buf.push('"');
}
if !style.is_empty() {
buf.push(' ');
buf.push_str("style=\"");
buf.push_str(style.trim_start().trim_end());
buf.push('"');
}
let inner_html = attribute_to_html(self.attributes, &mut buf);
buf.push('>');
buffer.push_sync(&buf);
@ -450,6 +386,56 @@ where
}
}
pub fn attribute_to_html<At, R>(attribute: At, buf: &mut String) -> String
where
At: Attribute<R>,
R: Renderer,
{
attributes_to_html(std::iter::once(attribute), buf)
}
pub fn attributes_to_html<At, R>(
attributes: impl IntoIterator<Item = At>,
buf: &mut String,
) -> String
where
At: Attribute<R>,
R: Renderer,
{
// `class` and `style` are created first, and pushed later
// this is because they can be filled by a mixture of values that include
// either the whole value (`class="..."` or `style="..."`) and individual
// classes and styles (`class:foo=true` or `style:height="40px"`), so they
// need to be filled during the whole attribute-creation process and then
// added
// String doesn't allocate until the first push, so this is cheap if there
// is no class or style on an element
let mut class = String::new();
let mut style = String::new();
let mut inner_html = String::new();
// inject regular attributes, and fill class and style
for attr in attributes {
attr.to_html(buf, &mut class, &mut style, &mut inner_html);
}
if !class.is_empty() {
buf.push(' ');
buf.push_str("class=\"");
buf.push_str(&escape_attr(class.trim_start().trim_end()));
buf.push('"');
}
if !style.is_empty() {
buf.push(' ');
buf.push_str("style=\"");
buf.push_str(&escape_attr(style.trim_start().trim_end()));
buf.push('"');
}
inner_html
}
pub struct ElementState<At, Ch, R: Renderer> {
pub el: R::Element,
pub attrs: At,