diff --git a/frontend_tests/node_tests/templates.js b/frontend_tests/node_tests/templates.js index 860e74c244..e53de17728 100644 --- a/frontend_tests/node_tests/templates.js +++ b/frontend_tests/node_tests/templates.js @@ -28,6 +28,21 @@ run_test("or", () => { assert.equal(html, "\n

last or

\n

true or

\n"); }); +run_test("let", () => { + const html = require("./templates/let.hbs")({ + outer_var: "hello", + }); + assert.equal( + html, + `\ + outer_var = hello + keyword_var = <b>escaped</b> + block_var = unescaped with hello + +`, + ); +}); + run_test("rendered_markdown", () => { const html = require("./templates/rendered_markdown.hbs")(); const expected_html = diff --git a/frontend_tests/node_tests/templates/let.hbs b/frontend_tests/node_tests/templates/let.hbs new file mode 100644 index 0000000000..9ee9a45700 --- /dev/null +++ b/frontend_tests/node_tests/templates/let.hbs @@ -0,0 +1,8 @@ +{{#let keyword_var="escaped"}} + {{#*inline "block_var"}} + unescaped with {{outer_var}} + {{/inline}} + outer_var = {{outer_var}} + keyword_var = {{keyword_var}} + block_var = {{block_var}} +{{/let}} diff --git a/static/js/templates.js b/static/js/templates.js index c66d059628..d0a4c16f6a 100644 --- a/static/js/templates.js +++ b/static/js/templates.js @@ -48,6 +48,26 @@ Handlebars.registerHelper({ not(a) { return !a || Handlebars.Utils.isEmpty(a); }, + ["let"](options) { + // Defines locally scoped variables. + // Example usage: + // {{#let name1="value1"}} + // {{#*inline "name2"}} + // value2 + // {{/inline}} + // Now {{name1}} and {{name2}} are in scope. + // {{/let}} + return options.fn({ + ...this, + ...options.hash, + ...Object.fromEntries( + Object.entries(options.fn.partials ?? {}).map(([name, value]) => [ + name, + new Handlebars.SafeString(value(this)), + ]), + ), + }); + }, }); // Note that this i18n caching strategy does not allow us to support diff --git a/tools/lib/template_parser.py b/tools/lib/template_parser.py index 9b94730ddb..4a85006711 100644 --- a/tools/lib/template_parser.py +++ b/tools/lib/template_parser.py @@ -133,6 +133,8 @@ def tokenize(text: str) -> List[Token]: elif looking_at_handlebars_start(): s = get_handlebars_tag(text, state.i) tag = s[3:-2].split()[0] + if tag.startswith("*"): + tag = tag[1:] kind = "handlebars_start" elif looking_at_handlebars_end(): s = get_handlebars_tag(text, state.i) diff --git a/webpack.config.ts b/webpack.config.ts index 230bfabd8a..36b5f6ff46 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -148,6 +148,7 @@ export default (_env: unknown, argv: {mode?: string}): webpack.Configuration[] = "and", "or", "not", + "let", "t", "tr", "rendered_markdown",