miki-template 2.2.2 → 2.3.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/.github/workflows/docs.yml +3 -1
- package/.github/workflows/release.yml +1 -0
- package/benchmarks/ejs-results.json +6 -6
- package/benchmarks/ejs.js +5 -3
- package/benchmarks/handlebars-results.json +6 -6
- package/benchmarks/handlebars.js +5 -8
- package/benchmarks/miki-results.json +6 -6
- package/benchmarks/miki.js +6 -3
- package/benchmarks/pug-results.json +6 -6
- package/benchmarks/pug.js +5 -3
- package/docs/api/async-render.md +88 -3
- package/docs/api/cache.md +90 -3
- package/docs/api/compile.md +131 -3
- package/docs/api/context-processors.md +80 -3
- package/docs/api/filters.md +223 -3
- package/docs/api/finder.md +97 -3
- package/docs/api/helpers.md +56 -3
- package/docs/api/i18n.md +160 -3
- package/docs/api/index.md +82 -28
- package/docs/api/libraries.md +210 -3
- package/docs/api/render-partial.md +84 -3
- package/docs/api/render.md +95 -3
- package/docs/api/security.md +148 -3
- package/docs/api/setup-express.md +78 -2
- package/docs/api/tags.md +138 -4
- package/docs/filter.md +0 -0
- package/docs/guide/advanced-usage.md +403 -6
- package/docs/guide/async-rendering.md +312 -4
- package/docs/guide/context-processors.md +261 -4
- package/docs/guide/custom-filters.md +315 -4
- package/docs/guide/custom-tags.md +275 -4
- package/docs/guide/filters.md +675 -3
- package/docs/guide/getting-started.md +109 -7
- package/docs/guide/installation.md +99 -4
- package/docs/guide/partial-templates.md +371 -4
- package/docs/guide/quick-start.md +228 -6
- package/docs/guide/security.md +348 -3
- package/docs/guide/tags.md +789 -6
- package/docs/guide/template-discovery.md +174 -4
- package/docs/guide/template-inheritance.md +277 -4
- package/docs/index.md +24 -42
- package/docs/integrations/elysia.md +4 -2
- package/docs/integrations/express.md +219 -219
- package/docs/integrations/fastify.md +4 -2
- package/docs/integrations/hono.md +4 -2
- package/docs/integrations/index.md +68 -68
- package/docs/integrations/koa.md +4 -2
- package/docs/integrations/nestjs.md +4 -2
- package/docs/integrations/tsed.md +4 -2
- package/docs/performance.md +45 -8
- package/ex.mjs +1 -1
- package/mkdocs.yml +0 -22
- package/overrides/main.html +1 -1
- package/package.json +1 -1
- package/requirements-docs.txt +2 -1
- package/src/codegen.js +905 -0
- package/src/context.js +42 -30
- package/src/filters.js +16 -0
- package/src/index.js +66 -61
- package/src/tags/control.js +15 -12
- package/src/utils.js +60 -0
- package/tests/filters.test.js +9 -0
- package/.github/workflows/npm-publish-github-packages.yml +0 -36
- package/docs/javascripts/extra.js +0 -174
- package/docs/stylesheets/extra.css +0 -819
- package/overrides/partials/footer.html +0 -9
|
@@ -1,311 +1,622 @@
|
|
|
1
|
-
# Custom Filters
|
|
1
|
+
# Custom Filters
|
|
2
|
+
|
|
3
|
+
|
|
2
4
|
|
|
3
5
|
Add your own filters to transform values in templates. miki-template's filter API mirrors Django's — filters are simply functions that receive a value and optional argument, and return the transformed value.
|
|
4
6
|
|
|
7
|
+
|
|
8
|
+
|
|
5
9
|
## Table of Contents
|
|
6
10
|
|
|
11
|
+
|
|
12
|
+
|
|
7
13
|
- [Register a Simple Filter](#register-a-simple-filter)
|
|
14
|
+
|
|
8
15
|
- [Filters with Arguments](#filters-with-arguments)
|
|
16
|
+
|
|
9
17
|
- [Multiple Arguments](#multiple-arguments)
|
|
18
|
+
|
|
10
19
|
- [Context-Aware Filters](#context-aware-filters)
|
|
20
|
+
|
|
11
21
|
- [SafeString Filters](#safestring-filters)
|
|
22
|
+
|
|
12
23
|
- [Async Filters](#async-filters)
|
|
24
|
+
|
|
13
25
|
- [Filter Registration Best Practices](#filter-registration-best-practices)
|
|
26
|
+
|
|
14
27
|
- [Chaining Custom Filters](#chaining-custom-filters)
|
|
15
28
|
|
|
29
|
+
|
|
30
|
+
|
|
16
31
|
---
|
|
17
32
|
|
|
33
|
+
|
|
34
|
+
|
|
18
35
|
## Register a Simple Filter
|
|
19
36
|
|
|
37
|
+
|
|
38
|
+
|
|
20
39
|
=== "CommonJS"
|
|
21
40
|
|
|
41
|
+
|
|
42
|
+
|
|
22
43
|
```javascript
|
|
44
|
+
|
|
23
45
|
const { registerFilter } = require('miki-template');
|
|
24
46
|
|
|
47
|
+
|
|
48
|
+
|
|
25
49
|
registerFilter('reverse', (val) => {
|
|
50
|
+
|
|
26
51
|
return String(val).split('').reverse().join('');
|
|
52
|
+
|
|
27
53
|
});
|
|
54
|
+
|
|
28
55
|
```
|
|
29
56
|
|
|
57
|
+
|
|
58
|
+
|
|
30
59
|
=== "ES Modules"
|
|
31
60
|
|
|
61
|
+
|
|
62
|
+
|
|
32
63
|
```javascript
|
|
64
|
+
|
|
33
65
|
import { registerFilter } from 'miki-template';
|
|
34
66
|
|
|
67
|
+
|
|
68
|
+
|
|
35
69
|
registerFilter('reverse', (val) => {
|
|
70
|
+
|
|
36
71
|
return String(val).split('').reverse().join('');
|
|
72
|
+
|
|
37
73
|
});
|
|
74
|
+
|
|
38
75
|
```
|
|
39
76
|
|
|
77
|
+
|
|
78
|
+
|
|
40
79
|
Use it in templates:
|
|
41
80
|
|
|
81
|
+
|
|
82
|
+
|
|
42
83
|
```html
|
|
84
|
+
|
|
43
85
|
{{ name|reverse }}
|
|
86
|
+
|
|
44
87
|
```
|
|
45
88
|
|
|
89
|
+
|
|
90
|
+
|
|
46
91
|
## Filters with Arguments
|
|
47
92
|
|
|
93
|
+
|
|
94
|
+
|
|
48
95
|
Filters can accept arguments after a colon:
|
|
49
96
|
|
|
97
|
+
|
|
98
|
+
|
|
50
99
|
=== "CommonJS"
|
|
51
100
|
|
|
101
|
+
|
|
102
|
+
|
|
52
103
|
```javascript
|
|
104
|
+
|
|
53
105
|
const { registerFilter } = require('miki-template');
|
|
54
106
|
|
|
107
|
+
|
|
108
|
+
|
|
55
109
|
registerFilter('multiply', (val, factor) => {
|
|
110
|
+
|
|
56
111
|
return Number(val) * Number(factor);
|
|
112
|
+
|
|
57
113
|
});
|
|
114
|
+
|
|
58
115
|
```
|
|
59
116
|
|
|
117
|
+
|
|
118
|
+
|
|
60
119
|
=== "ES Modules"
|
|
61
120
|
|
|
121
|
+
|
|
122
|
+
|
|
62
123
|
```javascript
|
|
124
|
+
|
|
63
125
|
import { registerFilter } from 'miki-template';
|
|
64
126
|
|
|
127
|
+
|
|
128
|
+
|
|
65
129
|
registerFilter('multiply', (val, factor) => {
|
|
130
|
+
|
|
66
131
|
return Number(val) * Number(factor);
|
|
132
|
+
|
|
67
133
|
});
|
|
134
|
+
|
|
68
135
|
```
|
|
69
136
|
|
|
137
|
+
|
|
138
|
+
|
|
70
139
|
Usage:
|
|
71
140
|
|
|
141
|
+
|
|
142
|
+
|
|
72
143
|
```html
|
|
144
|
+
|
|
73
145
|
{{ price|multiply:1.2 }}
|
|
146
|
+
|
|
74
147
|
```
|
|
75
148
|
|
|
149
|
+
|
|
150
|
+
|
|
76
151
|
## Multiple Arguments
|
|
77
152
|
|
|
153
|
+
|
|
154
|
+
|
|
78
155
|
Pass multiple arguments separated by commas:
|
|
79
156
|
|
|
157
|
+
|
|
158
|
+
|
|
80
159
|
=== "CommonJS"
|
|
81
160
|
|
|
161
|
+
|
|
162
|
+
|
|
82
163
|
```javascript
|
|
164
|
+
|
|
83
165
|
const { registerFilter } = require('miki-template');
|
|
84
166
|
|
|
167
|
+
|
|
168
|
+
|
|
85
169
|
registerFilter('format', (val, prefix, suffix) => {
|
|
170
|
+
|
|
86
171
|
return `${prefix}${val}${suffix}`;
|
|
172
|
+
|
|
87
173
|
});
|
|
174
|
+
|
|
88
175
|
```
|
|
89
176
|
|
|
177
|
+
|
|
178
|
+
|
|
90
179
|
=== "ES Modules"
|
|
91
180
|
|
|
181
|
+
|
|
182
|
+
|
|
92
183
|
```javascript
|
|
184
|
+
|
|
93
185
|
import { registerFilter } from 'miki-template';
|
|
94
186
|
|
|
187
|
+
|
|
188
|
+
|
|
95
189
|
registerFilter('format', (val, prefix, suffix) => {
|
|
190
|
+
|
|
96
191
|
return `${prefix}${val}${suffix}`;
|
|
192
|
+
|
|
97
193
|
});
|
|
194
|
+
|
|
98
195
|
```
|
|
99
196
|
|
|
197
|
+
|
|
198
|
+
|
|
100
199
|
Usage:
|
|
101
200
|
|
|
201
|
+
|
|
202
|
+
|
|
102
203
|
```html
|
|
204
|
+
|
|
103
205
|
{{ name|format:"<b>","</b>" }}
|
|
206
|
+
|
|
104
207
|
<!-- → "<b>Alice</b>" -->
|
|
208
|
+
|
|
105
209
|
```
|
|
106
210
|
|
|
211
|
+
|
|
212
|
+
|
|
107
213
|
### Real-World Example: Dynamic Currency Filter
|
|
108
214
|
|
|
215
|
+
|
|
216
|
+
|
|
109
217
|
=== "CommonJS"
|
|
110
218
|
|
|
219
|
+
|
|
220
|
+
|
|
111
221
|
```javascript
|
|
222
|
+
|
|
112
223
|
const { registerFilter, markSafe } = require('miki-template');
|
|
113
224
|
|
|
225
|
+
|
|
226
|
+
|
|
114
227
|
registerFilter('currency_dynamic', (val, code, locale = 'en-US') => {
|
|
228
|
+
|
|
115
229
|
const num = Number(val);
|
|
230
|
+
|
|
116
231
|
if (isNaN(num)) return '';
|
|
232
|
+
|
|
117
233
|
return new Intl.NumberFormat(locale, {
|
|
234
|
+
|
|
118
235
|
style: 'currency',
|
|
236
|
+
|
|
119
237
|
currency: code
|
|
238
|
+
|
|
120
239
|
}).format(num);
|
|
240
|
+
|
|
121
241
|
});
|
|
242
|
+
|
|
122
243
|
```
|
|
123
244
|
|
|
245
|
+
|
|
246
|
+
|
|
124
247
|
=== "ES Modules"
|
|
125
248
|
|
|
249
|
+
|
|
250
|
+
|
|
126
251
|
```javascript
|
|
252
|
+
|
|
127
253
|
import { registerFilter } from 'miki-template';
|
|
128
254
|
|
|
255
|
+
|
|
256
|
+
|
|
129
257
|
registerFilter('currency_dynamic', (val, code, locale = 'en-US') => {
|
|
258
|
+
|
|
130
259
|
const num = Number(val);
|
|
260
|
+
|
|
131
261
|
if (isNaN(num)) return '';
|
|
262
|
+
|
|
132
263
|
return new Intl.NumberFormat(locale, {
|
|
264
|
+
|
|
133
265
|
style: 'currency',
|
|
266
|
+
|
|
134
267
|
currency: code
|
|
268
|
+
|
|
135
269
|
}).format(num);
|
|
270
|
+
|
|
136
271
|
});
|
|
272
|
+
|
|
137
273
|
```
|
|
138
274
|
|
|
275
|
+
|
|
276
|
+
|
|
139
277
|
Template usage:
|
|
140
278
|
|
|
279
|
+
|
|
280
|
+
|
|
141
281
|
```html
|
|
282
|
+
|
|
142
283
|
<!-- €1,234.56 -->
|
|
284
|
+
|
|
143
285
|
{{ 1234.5|currency_dynamic:"EUR", "de-DE" }}
|
|
144
286
|
|
|
287
|
+
|
|
288
|
+
|
|
145
289
|
<!-- $1,234.56 -->
|
|
290
|
+
|
|
146
291
|
{{ 1234.5|currency_dynamic:"USD" }}
|
|
292
|
+
|
|
147
293
|
```
|
|
148
294
|
|
|
295
|
+
|
|
296
|
+
|
|
149
297
|
## Context-Aware Filters
|
|
150
298
|
|
|
299
|
+
|
|
300
|
+
|
|
151
301
|
Filters receive the rendering `context` as the third argument, enabling context-aware transformations:
|
|
152
302
|
|
|
303
|
+
|
|
304
|
+
|
|
153
305
|
=== "CommonJS"
|
|
154
306
|
|
|
307
|
+
|
|
308
|
+
|
|
155
309
|
```javascript
|
|
310
|
+
|
|
156
311
|
const { registerFilter } = require('miki-template');
|
|
157
312
|
|
|
313
|
+
|
|
314
|
+
|
|
158
315
|
registerFilter('currency', (val, symbol, ctx) => {
|
|
316
|
+
|
|
159
317
|
const num = Number(val);
|
|
318
|
+
|
|
160
319
|
if (isNaN(num)) return '';
|
|
320
|
+
|
|
161
321
|
const sym = symbol || ctx.currencySymbol || '$';
|
|
322
|
+
|
|
162
323
|
return sym + num.toFixed(2);
|
|
324
|
+
|
|
163
325
|
});
|
|
326
|
+
|
|
164
327
|
```
|
|
165
328
|
|
|
329
|
+
|
|
330
|
+
|
|
166
331
|
=== "ES Modules"
|
|
167
332
|
|
|
333
|
+
|
|
334
|
+
|
|
168
335
|
```javascript
|
|
336
|
+
|
|
169
337
|
import { registerFilter } from 'miki-template';
|
|
170
338
|
|
|
339
|
+
|
|
340
|
+
|
|
171
341
|
registerFilter('currency', (val, symbol, ctx) => {
|
|
342
|
+
|
|
172
343
|
const num = Number(val);
|
|
344
|
+
|
|
173
345
|
if (isNaN(num)) return '';
|
|
346
|
+
|
|
174
347
|
const sym = symbol || ctx.currencySymbol || '$';
|
|
348
|
+
|
|
175
349
|
return sym + num.toFixed(2);
|
|
350
|
+
|
|
176
351
|
});
|
|
352
|
+
|
|
177
353
|
```
|
|
178
354
|
|
|
355
|
+
|
|
356
|
+
|
|
179
357
|
Usage:
|
|
180
358
|
|
|
359
|
+
|
|
360
|
+
|
|
181
361
|
```html
|
|
362
|
+
|
|
182
363
|
{{ price|currency:"€" }}
|
|
364
|
+
|
|
183
365
|
<!-- The filter can also read ctx.currencySymbol for a default -->
|
|
366
|
+
|
|
184
367
|
```
|
|
185
368
|
|
|
369
|
+
|
|
370
|
+
|
|
186
371
|
**Real-world locale-aware formatter:**
|
|
187
372
|
|
|
373
|
+
|
|
374
|
+
|
|
188
375
|
```javascript
|
|
376
|
+
|
|
189
377
|
registerFilter('datetime', (val, format, ctx) => {
|
|
378
|
+
|
|
190
379
|
const locale = ctx.locale || 'en-US';
|
|
380
|
+
|
|
191
381
|
const d = new Date(val);
|
|
382
|
+
|
|
192
383
|
return new Intl.DateTimeFormat(locale, {
|
|
384
|
+
|
|
193
385
|
dateStyle: format === 'short' ? 'short' : 'full',
|
|
386
|
+
|
|
194
387
|
timeStyle: format === 'short' ? 'short' : undefined
|
|
388
|
+
|
|
195
389
|
}).format(d);
|
|
390
|
+
|
|
196
391
|
});
|
|
392
|
+
|
|
197
393
|
```
|
|
198
394
|
|
|
395
|
+
|
|
396
|
+
|
|
199
397
|
```html
|
|
398
|
+
|
|
200
399
|
{{ post.created_at|datetime:"full" }}
|
|
400
|
+
|
|
201
401
|
```
|
|
202
402
|
|
|
403
|
+
|
|
404
|
+
|
|
203
405
|
## SafeString Filters
|
|
204
406
|
|
|
407
|
+
|
|
408
|
+
|
|
205
409
|
Filters can return `SafeString` to prevent escaping — useful when generating HTML:
|
|
206
410
|
|
|
411
|
+
|
|
412
|
+
|
|
207
413
|
=== "CommonJS"
|
|
208
414
|
|
|
415
|
+
|
|
416
|
+
|
|
209
417
|
```javascript
|
|
418
|
+
|
|
210
419
|
const { registerFilter, markSafe } = require('miki-template');
|
|
211
420
|
|
|
421
|
+
|
|
422
|
+
|
|
212
423
|
registerFilter('badge', (val) => {
|
|
424
|
+
|
|
213
425
|
const color = val === 'active' ? 'green' : 'gray';
|
|
426
|
+
|
|
214
427
|
return markSafe(`<span class="badge badge-${color}">${val}</span>`);
|
|
428
|
+
|
|
215
429
|
});
|
|
430
|
+
|
|
216
431
|
```
|
|
217
432
|
|
|
433
|
+
|
|
434
|
+
|
|
218
435
|
=== "ES Modules"
|
|
219
436
|
|
|
437
|
+
|
|
438
|
+
|
|
220
439
|
```javascript
|
|
440
|
+
|
|
221
441
|
import { registerFilter, markSafe } from 'miki-template';
|
|
222
442
|
|
|
443
|
+
|
|
444
|
+
|
|
223
445
|
registerFilter('badge', (val) => {
|
|
446
|
+
|
|
224
447
|
const color = val === 'active' ? 'green' : 'gray';
|
|
448
|
+
|
|
225
449
|
return markSafe(`<span class="badge badge-${color}">${val}</span>`);
|
|
450
|
+
|
|
226
451
|
});
|
|
452
|
+
|
|
227
453
|
```
|
|
228
454
|
|
|
455
|
+
|
|
456
|
+
|
|
229
457
|
Usage:
|
|
230
458
|
|
|
459
|
+
|
|
460
|
+
|
|
231
461
|
```html
|
|
462
|
+
|
|
232
463
|
{{ user.status|badge }}
|
|
464
|
+
|
|
233
465
|
```
|
|
234
466
|
|
|
467
|
+
|
|
468
|
+
|
|
235
469
|
## Async Filters
|
|
236
470
|
|
|
471
|
+
|
|
472
|
+
|
|
237
473
|
Filters can be async by returning a Promise. Use `asyncRender()` to render templates with async filters:
|
|
238
474
|
|
|
475
|
+
|
|
476
|
+
|
|
239
477
|
=== "CommonJS"
|
|
240
478
|
|
|
479
|
+
|
|
480
|
+
|
|
241
481
|
```javascript
|
|
482
|
+
|
|
242
483
|
const { registerFilter } = require('miki-template');
|
|
243
484
|
|
|
485
|
+
|
|
486
|
+
|
|
244
487
|
registerFilter('fetch_user', async (val) => {
|
|
488
|
+
|
|
245
489
|
const res = await fetch(`https://api.example.com/users/${val}`);
|
|
490
|
+
|
|
246
491
|
const data = await res.json();
|
|
492
|
+
|
|
247
493
|
return data.display_name;
|
|
494
|
+
|
|
248
495
|
});
|
|
496
|
+
|
|
249
497
|
```
|
|
250
498
|
|
|
499
|
+
|
|
500
|
+
|
|
251
501
|
=== "ES Modules"
|
|
252
502
|
|
|
503
|
+
|
|
504
|
+
|
|
253
505
|
```javascript
|
|
506
|
+
|
|
254
507
|
import { registerFilter } from 'miki-template';
|
|
255
508
|
|
|
509
|
+
|
|
510
|
+
|
|
256
511
|
registerFilter('fetch_user', async (val) => {
|
|
512
|
+
|
|
257
513
|
const res = await fetch(`https://api.example.com/users/${val}`);
|
|
514
|
+
|
|
258
515
|
const data = await res.json();
|
|
516
|
+
|
|
259
517
|
return data.display_name;
|
|
518
|
+
|
|
260
519
|
});
|
|
520
|
+
|
|
261
521
|
```
|
|
262
522
|
|
|
523
|
+
|
|
524
|
+
|
|
263
525
|
Usage:
|
|
264
526
|
|
|
527
|
+
|
|
528
|
+
|
|
265
529
|
=== "CommonJS (asyncRender)"
|
|
266
530
|
|
|
531
|
+
|
|
532
|
+
|
|
267
533
|
```javascript
|
|
534
|
+
|
|
268
535
|
const { asyncRender } = require('miki-template');
|
|
269
536
|
|
|
537
|
+
|
|
538
|
+
|
|
270
539
|
const html = await asyncRender('Author: {{ user.id|fetch_user }}', { user: { id: 42 } });
|
|
540
|
+
|
|
271
541
|
```
|
|
272
542
|
|
|
543
|
+
|
|
544
|
+
|
|
273
545
|
=== "ES Modules"
|
|
274
546
|
|
|
547
|
+
|
|
548
|
+
|
|
275
549
|
```javascript
|
|
550
|
+
|
|
276
551
|
import { asyncRender } from 'miki-template';
|
|
277
552
|
|
|
553
|
+
|
|
554
|
+
|
|
278
555
|
const html = await asyncRender('Author: {{ user.id|fetch_user }}', { user: { id: 42 } });
|
|
556
|
+
|
|
279
557
|
```
|
|
280
558
|
|
|
559
|
+
|
|
560
|
+
|
|
281
561
|
> **Note:** Async filters only work with `asyncRender()` or `compiled.asyncRender()`. Using them with `render()` or `compiled.render()` will throw.
|
|
282
562
|
|
|
563
|
+
|
|
564
|
+
|
|
283
565
|
## Filter Registration Best Practices
|
|
284
566
|
|
|
567
|
+
|
|
568
|
+
|
|
285
569
|
1. **Handle null/undefined gracefully** — Return empty string or a fallback value.
|
|
570
|
+
|
|
286
571
|
2. **Return strings** — Filters should generally return string representations for template output.
|
|
572
|
+
|
|
287
573
|
3. **Don't mutate the input** — Treat values as immutable.
|
|
574
|
+
|
|
288
575
|
4. **Use `markSafe()` for HTML output** — Prevent auto-escaping when returning HTML.
|
|
576
|
+
|
|
289
577
|
5. **Validate arguments** — Coerce numeric arguments with `Number()` and handle `NaN`.
|
|
290
578
|
|
|
579
|
+
|
|
580
|
+
|
|
291
581
|
## Chaining Custom Filters
|
|
292
582
|
|
|
583
|
+
|
|
584
|
+
|
|
293
585
|
Custom filters chain the same way as built-in filters:
|
|
294
586
|
|
|
587
|
+
|
|
588
|
+
|
|
295
589
|
```html
|
|
590
|
+
|
|
296
591
|
{{ text|trim|highlight:"important"|safe }}
|
|
592
|
+
|
|
297
593
|
```
|
|
298
594
|
|
|
595
|
+
|
|
596
|
+
|
|
299
597
|
```javascript
|
|
598
|
+
|
|
300
599
|
registerFilter('trim', (val) => String(val || '').trim());
|
|
600
|
+
|
|
301
601
|
registerFilter('highlight', (val, term) => {
|
|
602
|
+
|
|
302
603
|
const re = new RegExp(`(${term})`, 'gi');
|
|
604
|
+
|
|
303
605
|
return markSafe(String(val).replace(re, '<mark>$1</mark>'));
|
|
606
|
+
|
|
304
607
|
});
|
|
608
|
+
|
|
305
609
|
```
|
|
306
610
|
|
|
611
|
+
|
|
612
|
+
|
|
307
613
|
## Next Steps
|
|
308
614
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
- [
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
- [Custom Tags](./custom-tags.md)
|
|
618
|
+
|
|
619
|
+
- [Advanced Usage](./advanced-usage.md)
|
|
620
|
+
|
|
621
|
+
- [API Reference: Filters](../api/filters.md)
|
|
622
|
+
|