mikel 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -17
- package/index.d.ts +13 -14
- package/index.js +142 -132
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -237,19 +237,6 @@ Partial metadata can be accessed using the `@partial` variable inside the partia
|
|
|
237
237
|
- `@partial.attributes`: the custom data provided to the partial (if any).
|
|
238
238
|
- `@partial.context`: the current rendering context.
|
|
239
239
|
|
|
240
|
-
### Inline partials
|
|
241
|
-
|
|
242
|
-
> Added in `v0.28.0`.
|
|
243
|
-
|
|
244
|
-
Inline partials allows you to define partials directly in your template. Use `>*` followed by the partial name to start the partial definition, and end the partial definition with a slash `/` followed by the partial name. For example, `{{>*foo}}` begins a partial definition called `foo`, and `{{/foo}}` ends it.
|
|
245
|
-
|
|
246
|
-
Example:
|
|
247
|
-
|
|
248
|
-
```javascript
|
|
249
|
-
const result = m(`{{>*foo}}Hello {{name}}!{{/foo}}{{>foo name="Bob"}}`, {});
|
|
250
|
-
// Output: 'Hello Bob!'
|
|
251
|
-
```
|
|
252
|
-
|
|
253
240
|
### Helpers
|
|
254
241
|
|
|
255
242
|
> Added in `v0.4.0`.
|
|
@@ -407,6 +394,78 @@ The `raw` helper allows to render the content of the block without evaluating it
|
|
|
407
394
|
console.log(m("{{#raw}}Hello {{name}}!{{/raw}}", {name: "Bob"})); // --> 'Hello {{name}}!'
|
|
408
395
|
```
|
|
409
396
|
|
|
397
|
+
#### slot
|
|
398
|
+
|
|
399
|
+
> Added in `v0.33.0`.
|
|
400
|
+
|
|
401
|
+
The `slot` helper allows you to capture a block of template content and store it under a named key. Captured slots become available through the special `@slot` state variable.
|
|
402
|
+
|
|
403
|
+
```javascript
|
|
404
|
+
const template = `
|
|
405
|
+
{{#slot "name"}}Bob{{/slot}}
|
|
406
|
+
|
|
407
|
+
Hello {{@slot.name}}!
|
|
408
|
+
`;
|
|
409
|
+
|
|
410
|
+
console.log(m(template, {})); // --> 'Hello Bob!'
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Slots are evaluated at render time, so they can contain variables, helpers, or any other template expressions. If the same slot name is defined more than once, **the last definition wins**.
|
|
414
|
+
|
|
415
|
+
#### macro
|
|
416
|
+
|
|
417
|
+
> Added in `v0.33.0`.
|
|
418
|
+
|
|
419
|
+
The `macro` helper allows you to register reusable chunks of content. Use `#macro` followed by the name of the reusable chunk.
|
|
420
|
+
|
|
421
|
+
Example:
|
|
422
|
+
|
|
423
|
+
```javascript
|
|
424
|
+
const template = `
|
|
425
|
+
{{#macro "foo"}}
|
|
426
|
+
Hello {{name}}!
|
|
427
|
+
{{/macro}}
|
|
428
|
+
|
|
429
|
+
{{#call name="Bob"}}{{/call}}
|
|
430
|
+
`;
|
|
431
|
+
|
|
432
|
+
console.log(m(template, {})); // --> 'Hello Bob!'
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Macros can be executed using the `#call` helper.
|
|
436
|
+
|
|
437
|
+
#### call
|
|
438
|
+
|
|
439
|
+
> Added in `v0.33.0`.
|
|
440
|
+
|
|
441
|
+
The `call` helper allows you to execute registered macros. Key-value arguments will be passed as a data to the content inside the macro:
|
|
442
|
+
|
|
443
|
+
```javascript
|
|
444
|
+
const template = `
|
|
445
|
+
{{#macro "sayHello"}}
|
|
446
|
+
Hello {{this.name}}!!
|
|
447
|
+
{{/macro}}
|
|
448
|
+
|
|
449
|
+
{{#call "sayHello" name="Bob"}}{{/call}}
|
|
450
|
+
`;
|
|
451
|
+
|
|
452
|
+
console.log(m(template, {})); // --> 'Hello Bob!!'
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Content inside the `#call` tag will be passed to the macro content in a `@content` state variable, similar as block partials.
|
|
456
|
+
|
|
457
|
+
```javascript
|
|
458
|
+
const template = `
|
|
459
|
+
{{#macro "foo"}}
|
|
460
|
+
Hello {{@content}}!!
|
|
461
|
+
{{/macro}}
|
|
462
|
+
|
|
463
|
+
{{#call "foo"}}Bob{{/call}}
|
|
464
|
+
`;
|
|
465
|
+
|
|
466
|
+
console.log(m(template, {})); // --> 'Hello Bob!!'
|
|
467
|
+
```
|
|
468
|
+
|
|
410
469
|
### Custom Helpers
|
|
411
470
|
|
|
412
471
|
> Added in `v0.5.0`.
|
|
@@ -438,7 +497,7 @@ Custom helper functions receive a single `params` object as argument, containing
|
|
|
438
497
|
- `args`: an array containing the variables with the helper is called in the template.
|
|
439
498
|
- `options`: an object containing the keyword arguments provided to the helper.
|
|
440
499
|
- `data`: the current data where the helper has been executed.
|
|
441
|
-
- `
|
|
500
|
+
- `state`: an object containing the state variables available in the current context (e.g., `@root`, `@index`, etc.).
|
|
442
501
|
- `fn`: a function that executes the template provided in the helper block and returns a string with the evaluated template in the provided context.
|
|
443
502
|
|
|
444
503
|
The helper function must return a string, which will be injected into the result string. Example:
|
|
@@ -501,11 +560,11 @@ Inside any helper block, you can access metadata about the current invocation th
|
|
|
501
560
|
- `@helper.options`: an object containing named (key-value) arguments.
|
|
502
561
|
- `@helper.context`: the current rendering context.
|
|
503
562
|
|
|
504
|
-
###
|
|
563
|
+
### State Variables
|
|
505
564
|
|
|
506
565
|
> Added in `v0.4.0`.
|
|
507
566
|
|
|
508
|
-
|
|
567
|
+
State Variables in Mikel provide convenient access to special values within your templates. These variables, denoted by the `@` symbol, allow users to interact with specific data contexts or values at runtime. State variables are usually generated by helpers like `#each`.
|
|
509
568
|
|
|
510
569
|
#### @root
|
|
511
570
|
|
|
@@ -567,7 +626,7 @@ Functions will receive a single `params` object as argument, containing the foll
|
|
|
567
626
|
- `args`: an array containing the variables with the function is called in the template.
|
|
568
627
|
- `options`: an object containing the keyword arguments provided to the function.
|
|
569
628
|
- `data`: the current data object where the function has been executed.
|
|
570
|
-
- `
|
|
629
|
+
- `state`: an object containing the state variables available in the current context (e.g., `@root`, `@index`, etc.).
|
|
571
630
|
|
|
572
631
|
Example:
|
|
573
632
|
|
package/index.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
export type MikelHelperCallback = (
|
|
2
|
+
data?: Record<string, any>,
|
|
3
|
+
state?: Record<string, any>,
|
|
4
|
+
) => string;
|
|
5
|
+
|
|
1
6
|
export type MikelHelper = (params: {
|
|
2
7
|
args: any[];
|
|
3
|
-
opt?: Record<string, any>;
|
|
4
8
|
options: Record<string, any>;
|
|
5
9
|
tokens: string[];
|
|
6
10
|
data: Record<string, any>;
|
|
7
|
-
|
|
8
|
-
fn:
|
|
11
|
+
state: Record<string, any>;
|
|
12
|
+
fn: MikelHelperCallback;
|
|
9
13
|
}) => string;
|
|
10
14
|
|
|
11
15
|
export type MikelPartial = {
|
|
@@ -15,29 +19,24 @@ export type MikelPartial = {
|
|
|
15
19
|
|
|
16
20
|
export type MikelFunction = (params: {
|
|
17
21
|
args: any[];
|
|
18
|
-
opt?: Record<string, any>;
|
|
19
22
|
options: Record<string,any>;
|
|
20
23
|
data: Record<string, any>;
|
|
21
|
-
|
|
24
|
+
state: Record<string, any>;
|
|
22
25
|
}) => string | void;
|
|
23
26
|
|
|
24
|
-
export type MikelContext = {
|
|
25
|
-
helpers: Record<string, MikelHelper>;
|
|
26
|
-
partials: Record<string, string | MikelPartial>;
|
|
27
|
-
functions: Record<string, MikelFunction>;
|
|
28
|
-
variables: Record<string, any>;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
27
|
export type MikelOptions = {
|
|
32
28
|
helpers?: Record<string, MikelHelper>;
|
|
33
29
|
partials?: Record<string, string | MikelPartial>;
|
|
34
30
|
functions?: Record<string, MikelFunction>;
|
|
35
|
-
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type MikelUseOptions = MikelOptions & {
|
|
34
|
+
initialState?: Record<string, any>;
|
|
36
35
|
};
|
|
37
36
|
|
|
38
37
|
export type Mikel = {
|
|
39
38
|
(template: string, data?: any): string;
|
|
40
|
-
use(options: Partial<
|
|
39
|
+
use(options: Partial<MikelUseOptions>): void;
|
|
41
40
|
addHelper(name: string, fn: MikelHelper): void;
|
|
42
41
|
removeHelper(name: string): void;
|
|
43
42
|
addFunction(name: string, fn: MikelFunction): void;
|
package/index.js
CHANGED
|
@@ -64,31 +64,31 @@ const tokenizeArgs = (str = "", tokens = [], strings = []) => {
|
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
// @description parse string arguments
|
|
67
|
-
const parseArgs = (str = "", data = {},
|
|
67
|
+
const parseArgs = (str = "", data = {}, state = {}, fns = {}, argv = [], opt = {}) => {
|
|
68
68
|
const [t, ...args] = tokenizeArgs(str.trim());
|
|
69
69
|
args.forEach(argStr => {
|
|
70
70
|
if (argStr.includes("=") && !argStr.startsWith(`"`)) {
|
|
71
71
|
const [k, v] = argStr.split("=");
|
|
72
|
-
opt[k] = parse(v, data,
|
|
72
|
+
opt[k] = parse(v, data, state, fns);
|
|
73
73
|
}
|
|
74
74
|
else if (argStr.startsWith("...")) {
|
|
75
|
-
const value = parse(argStr.replace(/^\.{3}/, ""), data,
|
|
75
|
+
const value = parse(argStr.replace(/^\.{3}/, ""), data, state, fns);
|
|
76
76
|
if (!!value && typeof value === "object") {
|
|
77
77
|
Array.isArray(value) ? argv.push(...value) : Object.assign(opt, value);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
else {
|
|
81
|
-
argv.push(parse(argStr, data,
|
|
81
|
+
argv.push(parse(argStr, data, state, fns));
|
|
82
82
|
}
|
|
83
83
|
});
|
|
84
84
|
return [t, argv, opt];
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
// @description evaluate an expression
|
|
88
|
-
const evaluateExpression = (str = "", data = {},
|
|
89
|
-
const [
|
|
88
|
+
const evaluateExpression = (str = "", data = {}, state = {}, fns = {}) => {
|
|
89
|
+
const [fnName, args, options] = parseArgs(str, data, state, fns);
|
|
90
90
|
if (typeof fns[fnName] === "function") {
|
|
91
|
-
return fns[fnName]({args,
|
|
91
|
+
return fns[fnName]({ args, options, data, state });
|
|
92
92
|
}
|
|
93
93
|
// if no function has been found with this name
|
|
94
94
|
// throw new Error(`Unknown function '${fnName}'`);
|
|
@@ -96,14 +96,14 @@ const evaluateExpression = (str = "", data = {}, vars = {}, fns = {}) => {
|
|
|
96
96
|
};
|
|
97
97
|
|
|
98
98
|
// @description parse a string value to a native type
|
|
99
|
-
const parse = (v, data = {},
|
|
99
|
+
const parse = (v, data = {}, state = {}, fns = {}) => {
|
|
100
100
|
if (v.startsWith("(") && v.endsWith(")")) {
|
|
101
|
-
return evaluateExpression(v.slice(1, -1).trim(), data,
|
|
101
|
+
return evaluateExpression(v.slice(1, -1).trim(), data, state, fns);
|
|
102
102
|
}
|
|
103
103
|
if ((v.startsWith(`"`) && v.endsWith(`"`)) || /^-?\d+\.?\d*$/.test(v) || v === "true" || v === "false" || v === "null") {
|
|
104
104
|
return JSON.parse(v);
|
|
105
105
|
}
|
|
106
|
-
return (v || "").startsWith("@") ? get(
|
|
106
|
+
return (v || "").startsWith("@") ? get(state, v.slice(1)) : get(data, v || ".");
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
// @description find the index of the closing token
|
|
@@ -122,6 +122,104 @@ const findClosingToken = (tokens, i, token) => {
|
|
|
122
122
|
throw new Error(`Unmatched section end: {{${token}}}`);
|
|
123
123
|
};
|
|
124
124
|
|
|
125
|
+
// @description internal method to compile the template
|
|
126
|
+
const compile = (ctx, tokens, output, data, state, index = 0, section = "") => {
|
|
127
|
+
let i = index;
|
|
128
|
+
while (i < tokens.length) {
|
|
129
|
+
if (i % 2 === 0) {
|
|
130
|
+
output.push(tokens[i]);
|
|
131
|
+
}
|
|
132
|
+
else if (tokens[i].startsWith("#") && typeof ctx.helpers[tokens[i].slice(1).trim().split(" ")[0]] === "function") {
|
|
133
|
+
const [t, args, opt] = parseArgs(tokens[i].slice(1), data, state);
|
|
134
|
+
const j = i + 1;
|
|
135
|
+
i = findClosingToken(tokens, j, t);
|
|
136
|
+
output.push(ctx.helpers[t]({
|
|
137
|
+
args: args,
|
|
138
|
+
options: opt,
|
|
139
|
+
tokens: tokens.slice(j, i),
|
|
140
|
+
data: data,
|
|
141
|
+
state: state,
|
|
142
|
+
fn: (blockData = {}, customBlockState = {}, blockOutput = []) => {
|
|
143
|
+
const blockState = {
|
|
144
|
+
...state,
|
|
145
|
+
...customBlockState,
|
|
146
|
+
helper: {
|
|
147
|
+
name: t,
|
|
148
|
+
options: opt || {},
|
|
149
|
+
args: args || [],
|
|
150
|
+
context: blockData,
|
|
151
|
+
},
|
|
152
|
+
parent: data,
|
|
153
|
+
root: state.root,
|
|
154
|
+
};
|
|
155
|
+
compile(ctx, tokens, blockOutput, blockData, blockState, j, t);
|
|
156
|
+
return blockOutput.join("");
|
|
157
|
+
},
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
else if (tokens[i].startsWith("#") || tokens[i].startsWith("^")) {
|
|
161
|
+
const t = tokens[i].slice(1).trim();
|
|
162
|
+
const value = get(data, t);
|
|
163
|
+
const negate = tokens[i].startsWith("^");
|
|
164
|
+
if (!negate && value && Array.isArray(value)) {
|
|
165
|
+
const j = i + 1;
|
|
166
|
+
(value.length > 0 ? value : [""]).forEach(item => {
|
|
167
|
+
i = compile(ctx, tokens, value.length > 0 ? output : [], item, state, j, t);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
const includeOutput = (!negate && !!value) || (negate && !!!value);
|
|
172
|
+
i = compile(ctx, tokens, includeOutput ? output : [], data, state, i + 1, t);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (tokens[i].startsWith(">")) {
|
|
176
|
+
const [t, args, opt] = parseArgs(tokens[i].replace(/^>{1,2}/, ""), data, state);
|
|
177
|
+
const blockContent = []; // to store partial block content
|
|
178
|
+
if (tokens[i].startsWith(">>")) {
|
|
179
|
+
i = compile(ctx, tokens, blockContent, data, state, i + 1, t);
|
|
180
|
+
}
|
|
181
|
+
if (typeof ctx.partials[t] === "string" || typeof ctx.partials[t]?.body === "string") {
|
|
182
|
+
const partialBody = ctx.partials[t]?.body || ctx.partials[t];
|
|
183
|
+
const partialData = args.length > 0 ? args[0] : (Object.keys(opt).length > 0 ? opt : data);
|
|
184
|
+
const partialState = {
|
|
185
|
+
...state,
|
|
186
|
+
content: blockContent.join(""),
|
|
187
|
+
partial: {
|
|
188
|
+
name: t,
|
|
189
|
+
attributes: ctx.partials[t]?.attributes || ctx.partials[t]?.data || {},
|
|
190
|
+
args: args || [],
|
|
191
|
+
options: opt || {},
|
|
192
|
+
context: partialData,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
compile(ctx, tokenize(partialBody), output, partialData, partialState, 0, "");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (tokens[i].startsWith("=")) {
|
|
199
|
+
output.push(evaluateExpression(tokens[i].slice(1), data, state, ctx.functions) ?? "");
|
|
200
|
+
}
|
|
201
|
+
else if (tokens[i].startsWith("/")) {
|
|
202
|
+
if (tokens[i].slice(1).trim() !== section) {
|
|
203
|
+
throw new Error(`Unmatched section end: {{${tokens[i]}}}`);
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const t = tokens[i].split("||").map(v => {
|
|
209
|
+
// check if the returned value should not be escaped
|
|
210
|
+
if (v.trim().startsWith("!")) {
|
|
211
|
+
return parse(v.trim().slice(1).trim(), data, state);
|
|
212
|
+
}
|
|
213
|
+
// escape the returned value
|
|
214
|
+
return escape(parse(v.trim(), data, state));
|
|
215
|
+
});
|
|
216
|
+
output.push(t.find(v => !!v) ?? "");
|
|
217
|
+
}
|
|
218
|
+
i = i + 1;
|
|
219
|
+
}
|
|
220
|
+
return i;
|
|
221
|
+
};
|
|
222
|
+
|
|
125
223
|
// @description default helpers
|
|
126
224
|
const defaultHelpers = {
|
|
127
225
|
"each": p => {
|
|
@@ -146,140 +244,52 @@ const defaultHelpers = {
|
|
|
146
244
|
"with": p => p.fn(p.args[0]),
|
|
147
245
|
"escape": p => escape(p.fn(p.data)),
|
|
148
246
|
"raw": p => untokenize(p.tokens),
|
|
247
|
+
"slot": params => {
|
|
248
|
+
if (typeof params.state.slot === "undefined") {
|
|
249
|
+
params.state.slot = {};
|
|
250
|
+
}
|
|
251
|
+
params.state.slot[params.args[0].trim()] = params.fn(params.data);
|
|
252
|
+
return "";
|
|
253
|
+
},
|
|
254
|
+
"macro": params => {
|
|
255
|
+
if (typeof params.state.macro === "undefined") {
|
|
256
|
+
params.state.macro = {};
|
|
257
|
+
}
|
|
258
|
+
params.state.macro[params.args[0].trim()] = untokenize(params.tokens).trim();
|
|
259
|
+
return "";
|
|
260
|
+
},
|
|
261
|
+
"call": params => {
|
|
262
|
+
const result = [];
|
|
263
|
+
const name = (params.args[0] || "").trim();
|
|
264
|
+
if (!!name && typeof params.state?.macro?.[name] === "string") {
|
|
265
|
+
compile(params.context, tokenize(params.state.macro[name]), result, params.options, {
|
|
266
|
+
...params.state,
|
|
267
|
+
content: params.fn(params.data),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return result.join("");
|
|
271
|
+
},
|
|
149
272
|
};
|
|
150
273
|
|
|
151
274
|
// @description create a new instance of mikel
|
|
152
275
|
const create = (options = {}) => {
|
|
153
|
-
const ctx = {
|
|
276
|
+
const ctx = Object.freeze({
|
|
154
277
|
helpers: Object.assign({}, defaultHelpers, options?.helpers || {}),
|
|
155
278
|
partials: Object.assign({}, options?.partials || {}),
|
|
156
|
-
functions: options?.functions || {},
|
|
157
|
-
|
|
158
|
-
};
|
|
159
|
-
// internal method to compile the template
|
|
160
|
-
const compile = (tokens, output, data, vars, index = 0, section = "") => {
|
|
161
|
-
let i = index;
|
|
162
|
-
while (i < tokens.length) {
|
|
163
|
-
if (i % 2 === 0) {
|
|
164
|
-
output.push(tokens[i]);
|
|
165
|
-
}
|
|
166
|
-
else if (tokens[i].startsWith("#") && typeof ctx.helpers[tokens[i].slice(1).trim().split(" ")[0]] === "function") {
|
|
167
|
-
const [t, args, opt] = parseArgs(tokens[i].slice(1), data, vars);
|
|
168
|
-
const j = i + 1;
|
|
169
|
-
i = findClosingToken(tokens, j, t);
|
|
170
|
-
output.push(ctx.helpers[t]({
|
|
171
|
-
args: args,
|
|
172
|
-
opt: opt, // deprecated
|
|
173
|
-
options: opt,
|
|
174
|
-
tokens: tokens.slice(j, i),
|
|
175
|
-
data: data,
|
|
176
|
-
variables: vars,
|
|
177
|
-
fn: (blockData = {}, blockVars = {}, blockOutput = []) => {
|
|
178
|
-
const helperVars = {
|
|
179
|
-
...vars,
|
|
180
|
-
...blockVars,
|
|
181
|
-
helper: {
|
|
182
|
-
name: t,
|
|
183
|
-
options: opt || {},
|
|
184
|
-
args: args || [],
|
|
185
|
-
context: blockData,
|
|
186
|
-
},
|
|
187
|
-
parent: data,
|
|
188
|
-
root: vars.root,
|
|
189
|
-
};
|
|
190
|
-
compile(tokens, blockOutput, blockData, helperVars, j, t);
|
|
191
|
-
return blockOutput.join("");
|
|
192
|
-
},
|
|
193
|
-
}));
|
|
194
|
-
}
|
|
195
|
-
else if (tokens[i].startsWith("#") || tokens[i].startsWith("^")) {
|
|
196
|
-
const t = tokens[i].slice(1).trim();
|
|
197
|
-
const value = get(data, t);
|
|
198
|
-
const negate = tokens[i].startsWith("^");
|
|
199
|
-
if (!negate && value && Array.isArray(value)) {
|
|
200
|
-
const j = i + 1;
|
|
201
|
-
(value.length > 0 ? value : [""]).forEach(item => {
|
|
202
|
-
i = compile(tokens, value.length > 0 ? output : [], item, vars, j, t);
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
const includeOutput = (!negate && !!value) || (negate && !!!value);
|
|
207
|
-
i = compile(tokens, includeOutput ? output : [], data, vars, i + 1, t);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
else if (tokens[i].startsWith(">*")) {
|
|
211
|
-
const t = tokens[i].slice(2).trim(), partialTokens = tokens.slice(i + 1);
|
|
212
|
-
const lastIndex = partialTokens.findIndex((token, j) => {
|
|
213
|
-
return j % 2 !== 0 && token.trim().startsWith("/") && token.trim().endsWith(t);
|
|
214
|
-
});
|
|
215
|
-
if (typeof ctx.partials[t] === "undefined") {
|
|
216
|
-
ctx.partials[t] = untokenize(partialTokens.slice(0, lastIndex));
|
|
217
|
-
}
|
|
218
|
-
i = i + lastIndex + 1;
|
|
219
|
-
}
|
|
220
|
-
else if (tokens[i].startsWith(">")) {
|
|
221
|
-
const [t, args, opt] = parseArgs(tokens[i].replace(/^>{1,2}/, ""), data, vars);
|
|
222
|
-
const blockContent = []; // to store partial block content
|
|
223
|
-
if (tokens[i].startsWith(">>")) {
|
|
224
|
-
i = compile(tokens, blockContent, data, vars, i + 1, t);
|
|
225
|
-
}
|
|
226
|
-
if (typeof ctx.partials[t] === "string" || typeof ctx.partials[t]?.body === "string") {
|
|
227
|
-
const newData = args.length > 0 ? args[0] : (Object.keys(opt).length > 0 ? opt : data);
|
|
228
|
-
const newVars = {
|
|
229
|
-
...vars,
|
|
230
|
-
content: blockContent.join(""),
|
|
231
|
-
partial: {
|
|
232
|
-
name: t,
|
|
233
|
-
attributes: ctx.partials[t]?.attributes || ctx.partials[t]?.data || {},
|
|
234
|
-
args: args || [],
|
|
235
|
-
options: opt || {},
|
|
236
|
-
context: newData,
|
|
237
|
-
},
|
|
238
|
-
};
|
|
239
|
-
compile(tokenize(ctx.partials[t]?.body || ctx.partials[t]), output, newData, newVars, 0, "");
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
else if (tokens[i].startsWith("=")) {
|
|
243
|
-
output.push(evaluateExpression(tokens[i].slice(1), data, vars, ctx.functions) ?? "");
|
|
244
|
-
}
|
|
245
|
-
else if (tokens[i].startsWith("/")) {
|
|
246
|
-
if (tokens[i].slice(1).trim() !== section) {
|
|
247
|
-
throw new Error(`Unmatched section end: {{${tokens[i]}}}`);
|
|
248
|
-
}
|
|
249
|
-
break;
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
const t = tokens[i].split("||").map(v => {
|
|
253
|
-
// check if the returned value should not be escaped
|
|
254
|
-
if (v.trim().startsWith("!")) {
|
|
255
|
-
return parse(v.trim().slice(1).trim(), data, vars);
|
|
256
|
-
}
|
|
257
|
-
// escape the returned value
|
|
258
|
-
return escape(parse(v.trim(), data, vars));
|
|
259
|
-
});
|
|
260
|
-
output.push(t.find(v => !!v) ?? "");
|
|
261
|
-
}
|
|
262
|
-
i = i + 1;
|
|
263
|
-
}
|
|
264
|
-
return i;
|
|
265
|
-
};
|
|
279
|
+
functions: Object.assign({}, options?.functions || {}),
|
|
280
|
+
initialState: {}, // Object.assign({}, options?.initialState || {}),
|
|
281
|
+
});
|
|
266
282
|
// entry method to compile the template with the provided data object
|
|
267
283
|
const compileTemplate = (template, data = {}, output = []) => {
|
|
268
|
-
compile(tokenize(template), output, data, {root: data
|
|
284
|
+
compile(ctx, tokenize(template), output, data, { ...ctx.initialState, root: data }, 0, "");
|
|
269
285
|
return output.join("");
|
|
270
286
|
};
|
|
271
287
|
// assign api methods and return method to compile the template
|
|
272
288
|
return Object.assign(compileTemplate, {
|
|
273
|
-
use: newOptions => {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
else if (!!newOptions && typeof newOptions === "object") {
|
|
278
|
-
["helpers", "functions", "partials", "variables"].forEach(field => {
|
|
279
|
-
Object.assign(ctx[field], newOptions?.[field] || {});
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
return compileTemplate;
|
|
289
|
+
use: (newOptions = {}) => {
|
|
290
|
+
["helpers", "functions", "partials", "initialState"].forEach(field => {
|
|
291
|
+
Object.assign(ctx[field], newOptions?.[field] || {});
|
|
292
|
+
});
|
|
283
293
|
},
|
|
284
294
|
addHelper: (name, fn) => ctx.helpers[name] = fn,
|
|
285
295
|
removeHelper: name => delete ctx.helpers[name],
|