Fixes #678
parent
9bb72d01b2
commit
d0ebda0d7a
|
@ -135,11 +135,12 @@ const TemplateDirective: MarkdownConfig = {
|
|||
{ name: "TemplateExpressionDirective" },
|
||||
{ name: "TemplateIfStartDirective", style: ct.DirectiveTag },
|
||||
{ name: "TemplateEachStartDirective", style: ct.DirectiveTag },
|
||||
{ name: "TemplateEachVarStartDirective", style: ct.DirectiveTag },
|
||||
{ name: "TemplateLetStartDirective", style: ct.DirectiveTag },
|
||||
{ name: "TemplateIfEndDirective", style: ct.DirectiveTag },
|
||||
{ name: "TemplateEachEndDirective", style: ct.DirectiveTag },
|
||||
{ name: "TemplateLetEndDirective", style: ct.DirectiveTag },
|
||||
{ name: "TemplateLetVar", style: t.variableName },
|
||||
{ name: "TemplateVar", style: t.variableName },
|
||||
{ name: "TemplateDirectiveMark", style: ct.DirectiveMarkTag },
|
||||
],
|
||||
parseInline: [
|
||||
|
@ -182,6 +183,64 @@ const TemplateDirective: MarkdownConfig = {
|
|||
const endPos = pos + valueLength + 1;
|
||||
let bodyEl: any;
|
||||
|
||||
// Is this an let block directive?
|
||||
const openLetBlockMatch = /^(\s*#let\s*)(@\w+)(\s*=\s*)(.+)$/s.exec(
|
||||
bodyText,
|
||||
);
|
||||
if (openLetBlockMatch) {
|
||||
const [_, directiveStart, varName, eq, expr] = openLetBlockMatch;
|
||||
const parsedExpression = highlightingExpressionParser.parse(
|
||||
expr,
|
||||
);
|
||||
bodyEl = cx.elt(
|
||||
"TemplateLetStartDirective",
|
||||
pos + 2,
|
||||
endPos - 2,
|
||||
[
|
||||
cx.elt(
|
||||
"TemplateVar",
|
||||
pos + 2 + directiveStart.length,
|
||||
pos + 2 + directiveStart.length + varName.length,
|
||||
),
|
||||
cx.elt(
|
||||
parsedExpression,
|
||||
pos + 2 + directiveStart.length + varName.length + eq.length,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (!bodyEl) {
|
||||
// Is this an #each @p = block directive?
|
||||
const openEachVariableBlockMatch =
|
||||
/^(\s*#each\s*)(@\w+)(\s*in\s*)(.+)$/s.exec(
|
||||
bodyText,
|
||||
);
|
||||
if (openEachVariableBlockMatch) {
|
||||
const [_, directiveStart, varName, eq, expr] =
|
||||
openEachVariableBlockMatch;
|
||||
const parsedExpression = highlightingExpressionParser.parse(
|
||||
expr,
|
||||
);
|
||||
bodyEl = cx.elt(
|
||||
"TemplateEachVarStartDirective",
|
||||
pos + 2,
|
||||
endPos - 2,
|
||||
[
|
||||
cx.elt(
|
||||
"TemplateVar",
|
||||
pos + 2 + directiveStart.length,
|
||||
pos + 2 + directiveStart.length + varName.length,
|
||||
),
|
||||
cx.elt(
|
||||
parsedExpression,
|
||||
pos + 2 + directiveStart.length + varName.length + eq.length,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!bodyEl) {
|
||||
// Is this an open block directive?
|
||||
const openBlockMatch = /^(\s*#(if|each)\s*)(.+)$/s.exec(bodyText);
|
||||
if (openBlockMatch) {
|
||||
|
@ -199,34 +258,6 @@ const TemplateDirective: MarkdownConfig = {
|
|||
[cx.elt(parsedExpression, pos + 2 + directiveStart.length)],
|
||||
);
|
||||
}
|
||||
|
||||
if (!bodyEl) {
|
||||
// Is this an open block directive?
|
||||
const openLetBlockMatch = /^(\s*#let\s*)(@\w+)(\s*=\s*)(.+)$/s.exec(
|
||||
bodyText,
|
||||
);
|
||||
if (openLetBlockMatch) {
|
||||
const [_, directiveStart, varName, eq, expr] = openLetBlockMatch;
|
||||
const parsedExpression = highlightingExpressionParser.parse(
|
||||
expr,
|
||||
);
|
||||
bodyEl = cx.elt(
|
||||
"TemplateLetStartDirective",
|
||||
pos + 2,
|
||||
endPos - 2,
|
||||
[
|
||||
cx.elt(
|
||||
"TemplateLetVar",
|
||||
pos + 2 + directiveStart.length,
|
||||
pos + 2 + directiveStart.length + varName.length,
|
||||
),
|
||||
cx.elt(
|
||||
parsedExpression,
|
||||
pos + 2 + directiveStart.length + varName.length + eq.length,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!bodyEl) {
|
||||
|
|
|
@ -135,6 +135,14 @@ Deno.test("Test template", async () => {
|
|||
"1\n2\n3\n",
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
await parseAndRender(
|
||||
"{{#each @v in [1, 2, 3]}}\n{{@v}}\n{{/each}}",
|
||||
true,
|
||||
),
|
||||
"1\n2\n3\n",
|
||||
);
|
||||
|
||||
function parseAndRender(template: string, value: any): Promise<string> {
|
||||
const parsedTemplate = parseTemplate(template);
|
||||
return renderTemplate(parsedTemplate, value, variables, functionMap);
|
||||
|
|
|
@ -2,10 +2,7 @@ import { AST } from "$sb/lib/tree.ts";
|
|||
import { evalQueryExpression } from "$sb/lib/query_expression.ts";
|
||||
import { expressionToKvQueryExpression } from "$sb/lib/parse-query.ts";
|
||||
import { FunctionMap } from "$sb/types.ts";
|
||||
import {
|
||||
jsonObjectToMDTable,
|
||||
jsonToMDTable,
|
||||
} from "../../plugs/template/util.ts";
|
||||
import { jsonToMDTable } from "../../plugs/template/util.ts";
|
||||
|
||||
export async function renderTemplate(
|
||||
ast: AST,
|
||||
|
@ -50,6 +47,13 @@ async function renderTemplateElement(
|
|||
variables,
|
||||
functionMap,
|
||||
);
|
||||
case "EachVarDirective":
|
||||
return await renderEachVarDirective(
|
||||
ast,
|
||||
value,
|
||||
variables,
|
||||
functionMap,
|
||||
);
|
||||
case "IfDirective":
|
||||
return await renderIfDirective(ast, value, variables, functionMap);
|
||||
case "LetDirective":
|
||||
|
@ -101,6 +105,48 @@ async function renderExpressionDirective(
|
|||
}
|
||||
}
|
||||
|
||||
async function renderEachVarDirective(
|
||||
ast: AST,
|
||||
value: any[],
|
||||
variables: Record<string, any>,
|
||||
functionMap: FunctionMap,
|
||||
): Promise<string> {
|
||||
const [_eachVarDirective, name, expression, template] = ast;
|
||||
const expr = expressionToKvQueryExpression(expression);
|
||||
const values = await evalQueryExpression(
|
||||
expr,
|
||||
value,
|
||||
variables,
|
||||
functionMap,
|
||||
);
|
||||
if (!Array.isArray(values)) {
|
||||
throw new Error(
|
||||
`Expecting a list expression for #each var directive, instead got ${values}`,
|
||||
);
|
||||
}
|
||||
const resultPieces: string[] = [];
|
||||
for (const itemValue of values) {
|
||||
const localVariables = { ...variables, [name as any]: itemValue };
|
||||
try {
|
||||
resultPieces.push(
|
||||
await renderTemplate(
|
||||
template,
|
||||
value,
|
||||
localVariables,
|
||||
functionMap,
|
||||
),
|
||||
);
|
||||
} catch (e: any) {
|
||||
throw new Error(
|
||||
`Error rendering #each directive: ${e.message} for item ${
|
||||
JSON.stringify(itemValue)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return resultPieces.join("");
|
||||
}
|
||||
|
||||
async function renderEachDirective(
|
||||
ast: AST,
|
||||
value: any[],
|
||||
|
@ -182,11 +228,11 @@ async function renderLetDirective(
|
|||
variables,
|
||||
functionMap,
|
||||
);
|
||||
const newGlobalVariables = { ...variables, [name as any]: val };
|
||||
const newVariables = { ...variables, [name as any]: val };
|
||||
return await renderTemplate(
|
||||
template,
|
||||
value,
|
||||
newGlobalVariables,
|
||||
newVariables,
|
||||
functionMap,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -75,6 +75,10 @@ function processTree(tree: AST): AST {
|
|||
const body = bodyElements.map(processTree);
|
||||
switch (blockType) {
|
||||
case "each": {
|
||||
const eachExpr = blockTextContent.trim();
|
||||
const eachVarMatch = eachExpr.match(/@(\w+)\s*in\s*(.+)$/s);
|
||||
if (!eachVarMatch) {
|
||||
// Not a each var declaration, just an expression
|
||||
const expressionTree = parseTreeToAST(parse(
|
||||
expressionLanguage,
|
||||
blockTextContent.trim(),
|
||||
|
@ -85,6 +89,18 @@ function processTree(tree: AST): AST {
|
|||
...stripInitialNewline(body),
|
||||
]];
|
||||
}
|
||||
// This is a #each @p = version
|
||||
const expressionTree = parseTreeToAST(parse(
|
||||
expressionLanguage,
|
||||
eachVarMatch[2],
|
||||
));
|
||||
return [
|
||||
"EachVarDirective",
|
||||
eachVarMatch[1],
|
||||
expressionTree[1],
|
||||
["Template", ...stripInitialNewline(body)],
|
||||
];
|
||||
}
|
||||
case "let": {
|
||||
const letExpr = blockTextContent.trim();
|
||||
const letMatch = letExpr.match(/@(\w+)\s*=\s*(.+)$/s);
|
||||
|
@ -156,7 +172,8 @@ function stripInitialNewline(body: any[]) {
|
|||
|
||||
// After each block directive, strip the next newline
|
||||
if (
|
||||
["IfDirective", "EachDirective", "LetDirective"].includes(el[1][0])
|
||||
["IfDirective", "EachDirective", "EachVarDirective", "LetDirective"]
|
||||
.includes(el[1][0])
|
||||
) {
|
||||
// console.log("Got a block directive, consider stripping the next one", el);
|
||||
stripNext = true;
|
||||
|
|
|
@ -4,7 +4,6 @@ import { events } from "$sb/syscalls.ts";
|
|||
import { QueryProviderEvent } from "$sb/app_event.ts";
|
||||
import { resolvePath } from "$sb/lib/resolve.ts";
|
||||
import { renderQueryTemplate } from "../template/util.ts";
|
||||
import { parse } from "../../common/markdown_parser/parse_tree.ts";
|
||||
|
||||
export async function query(
|
||||
query: string,
|
||||
|
|
|
@ -60,14 +60,22 @@ You can also add an optional `else` clause:
|
|||
```
|
||||
|
||||
# each directive
|
||||
To iterate over a collection use an `#each` directive. On each iteration, the current item that is iterated over will be set as the active object (accessible via `.` and its attributes via the `attribute` syntax):
|
||||
To iterate over a collection use an `#each` directive. There are two variants of `#each`, one with and one without variable assignment:
|
||||
|
||||
* `#each @varname in expression` repeats the body of this directive assigning every value to `@varname` one by one
|
||||
* `#each expression` repeats the body of this directive assigning every value to `.` one by one.
|
||||
|
||||
```template
|
||||
Counting to 3:
|
||||
Counting to 3 with a variable name:
|
||||
{{#each [1, 2, 3]}}
|
||||
* {{.}}
|
||||
{{/each}}
|
||||
|
||||
And using a variable name iterator:
|
||||
{{#each @v in [1, 2, 3]}}
|
||||
* {{@v}}
|
||||
{{/each}}
|
||||
|
||||
Iterating over the three last modified pages:
|
||||
{{#each {page order by lastModified desc limit 3}}}
|
||||
* {{name}}
|
||||
|
|
Loading…
Reference in New Issue