Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

- Each item of the tracking component accepts a `link`. ([#900](https://github.com/sqlpage/SQLPage/issues/900))

- Custom components can call inline partials inside `each_row` and after it, when the partials are defined before `each_row`. ([#846](https://github.com/sqlpage/SQLPage/issues/846))

## v0.47.0 (2026-10-15)
- Redesigned the official website homepage with live SQLPage examples, an interactive database sculpture, flowing section transitions, responsive layouts, and direct links to documentation and deployment guides.
- For sites that store their pages in a database, checking for page updates no longer makes other pages wait when the database is busy. In a test, another page loaded in under 1 ms instead of about 250 ms.
Expand Down
19 changes: 19 additions & 0 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,25 @@ mod tests {
Ok(())
}

#[actix_web::test]
async fn inline_partials_defined_before_each_row_render_in_every_part() -> anyhow::Result<()> {
let template = Template::compile(
"{{#*inline \"item\"}}[{{x}}]{{/inline}}\
{{> item x=\"start\"}}{{#each_row}}{{> item}}{{/each_row}}{{> item x=\"end\"}}",
)?;
let split = split_template(template);
let mut output = Vec::new();
let config = app_config::tests::test_config();
let app_state = Arc::new(AppState::init(&config).await.unwrap());
let mut rdr = SplitTemplateRenderer::new(Arc::new(split), app_state, 0, 0);
rdr.render_start(&mut output, json!(null))?;
rdr.render_item(&mut output, json!({"x": 1}))?;
rdr.render_item(&mut output, json!({"x": 2}))?;
rdr.render_end(&mut output)?;
assert_eq!(String::from_utf8_lossy(&output), "[start][1][2][end]");
Ok(())
}

#[actix_web::test]
async fn test_delayed() -> anyhow::Result<()> {
let template = Template::compile(
Expand Down
23 changes: 23 additions & 0 deletions src/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ pub fn split_template(mut original: Template) -> SplitTemplate {
after_list.name = Some(format!("{original_name} after each block"));
after_list.elements = elements_after;
after_list.mapping = mapping_after;
// Each part renders in a fresh render context, which is where handlebars
// registers inline partials, so every part has to define them again.
prepend_inline_partials(&before_list, &mut list_content);
prepend_inline_partials(&before_list, &mut after_list);
SplitTemplate {
before_list,
list_content,
Expand All @@ -64,6 +68,25 @@ impl AsyncFromStrWithState for SplitTemplate {
}
}

fn prepend_inline_partials(defining: &Template, using: &mut Template) {
let (elements, mapping): (Vec<_>, Vec<_>) = defining
.elements
.iter()
.zip(&defining.mapping)
.filter(|(element, _)| is_inline_partial(element))
.map(|(element, position)| (element.clone(), position.clone()))
.unzip();
using.elements.splice(0..0, elements);
using.mapping.splice(0..0, mapping);
}

fn is_inline_partial(element: &TemplateElement) -> bool {
use handlebars::template::Parameter::Name;
matches!(element,
TemplateElement::DecoratorBlock(decorator)
if matches!(&decorator.name, Name(name) if name == "inline"))
}

fn is_template_list_item(element: &TemplateElement) -> bool {
use Parameter::Name;
use handlebars::template::Parameter;
Expand Down
Loading