miki-template 1.2.0 → 1.3.3
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/release-notes/v1.3.1.md +55 -0
- package/CHANGELOG.md +72 -0
- package/README.md +43 -26
- package/assets/banner.png +0 -0
- package/benchmarks/stress.mjs +647 -0
- package/dir/base.html +23 -0
- package/dir/cmpnt.html +11 -0
- package/dir/footer.html +3 -0
- package/dir/home.html +80 -0
- package/dir/navbar.html +9 -0
- package/docs/api.md +20 -3
- package/docs/filters.md +301 -133
- package/docs/partialdef.md +30 -1
- package/docs/tags.md +63 -0
- package/docs/usage.md +50 -3
- package/eslint.config.mjs +9 -1
- package/ex.mjs +33 -0
- package/miki-template-extension/.github/workflows/ci.yml +116 -0
- package/miki-template-extension/.vscodeignore +7 -0
- package/miki-template-extension/CHANGELOG.md +99 -0
- package/miki-template-extension/README.md +244 -53
- package/miki-template-extension/extension.js +1013 -0
- package/miki-template-extension/icon.png +0 -0
- package/miki-template-extension/miki-template-1.7.1.vsix +0 -0
- package/miki-template-extension/package.json +244 -10
- package/miki-template-extension/snippets/miki-template.json +612 -72
- package/miki-template-extension/syntaxes/language-configuration.json +101 -13
- package/miki-template-extension/syntaxes/miki-template.tmLanguage.json +270 -61
- package/miki-template-extension/tests/grammar-tests.json +162 -0
- package/miki-template-extension/tests/run-grammar-tests.js +82 -0
- package/package.json +7 -4
- package/scripts/build-vsix.js +129 -0
- package/scripts/build-vsix.ps1 +15 -0
- package/src/cache.js +41 -2
- package/src/context.js +9 -5
- package/src/context_processors.js +9 -2
- package/src/esm.mjs +12 -0
- package/src/filters.js +472 -24
- package/src/index.js +571 -85
- package/src/lexer.js +76 -54
- package/src/libraries.js +134 -3
- package/src/parser.js +22 -2
- package/src/security.js +4 -2
- package/src/tags/control.js +150 -21
- package/src/tags/extra.js +154 -0
- package/src/tags/i18n.js +49 -23
- package/src/tags/inheritance.js +142 -23
- package/src/tags/util.js +102 -24
- package/tests/esm.test.mjs +37 -2
- package/tests/filters.test.js +155 -0
- package/tests/integration/README.md +32 -0
- package/tests/integration/features.test.cjs +1681 -0
- package/tests/integration/features.test.mjs +1697 -0
- package/tests/integration/templates/base.miki +6 -0
- package/tests/integration/templates/child.miki +6 -0
- package/tests/integration/templates/index.html +17 -0
- package/tests/parser.test.js +5 -3
- package/tests/partialdef.test.js +40 -1
- package/tests/tags.test.js +30 -0
- package/miki-template-1.2.0.vsix +0 -0
package/docs/filters.md
CHANGED
|
@@ -61,12 +61,6 @@ Returns the number of words (whitespace-separated tokens).
|
|
|
61
61
|
{{ " "|wordcount }} → 0
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
### `striptags`
|
|
65
|
-
Removes all HTML/XML tags from the string.
|
|
66
|
-
```html
|
|
67
|
-
{{ "<p>Hello <b>World</b></p>"|striptags }} → "Hello World"
|
|
68
|
-
```
|
|
69
|
-
|
|
70
64
|
### `truncatewords:N`
|
|
71
65
|
Truncates the string to approximately N words, appending `...`.
|
|
72
66
|
```html
|
|
@@ -80,6 +74,12 @@ Truncates to N characters (including the `...` suffix if truncation occurs).
|
|
|
80
74
|
{{ "Hi"|truncatechars:5 }} → "Hi" (no truncation needed)
|
|
81
75
|
```
|
|
82
76
|
|
|
77
|
+
### `truncatechars_html:N`
|
|
78
|
+
Like `truncatechars` but respects HTML tags — tags are preserved in full and only visible text counts toward the limit.
|
|
79
|
+
```html
|
|
80
|
+
{{ "<p>Hello world</p>"|truncatechars_html:10 }} → "<p>Hello worl...</p>"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
83
|
### `linebreaks`
|
|
84
84
|
Converts newlines into paragraphs (`<p>`) and standalone line breaks into `<br>`.
|
|
85
85
|
```html
|
|
@@ -94,6 +94,13 @@ Converts all newlines to `<br>` tags. Does **not** wrap in `<p>` tags.
|
|
|
94
94
|
<!-- Output: Line one<br>Line two -->
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
### `urlize`
|
|
98
|
+
Automatically converts URLs in text into clickable `<a>` links.
|
|
99
|
+
```html
|
|
100
|
+
{{ "visit https://example.com for more"|urlize }}
|
|
101
|
+
<!-- Output: visit <a href="https://example.com">https://example.com</a> for more -->
|
|
102
|
+
```
|
|
103
|
+
|
|
97
104
|
### `cut:value`
|
|
98
105
|
Removes all occurrences of the specified value from the string.
|
|
99
106
|
```html
|
|
@@ -102,9 +109,9 @@ Removes all occurrences of the specified value from the string.
|
|
|
102
109
|
```
|
|
103
110
|
|
|
104
111
|
### `addslashes`
|
|
105
|
-
Adds backslashes before single quotes, double quotes, and backslashes
|
|
112
|
+
Adds backslashes before single quotes, double quotes, and backslashes.
|
|
106
113
|
```html
|
|
107
|
-
{{ 'He said "Hello"|addslashes }} → 'He said \"Hello\"'
|
|
114
|
+
{{ 'He said "Hello"'|addslashes }} → 'He said \"Hello\"'
|
|
108
115
|
```
|
|
109
116
|
|
|
110
117
|
### `removetags:tag1,tag2,...`
|
|
@@ -113,6 +120,33 @@ Removes the named HTML tags (and their contents).
|
|
|
113
120
|
{{ "<p>Hello</p><b>World</b>"|removetags:"p,b" }} → "HelloWorld"
|
|
114
121
|
```
|
|
115
122
|
|
|
123
|
+
### `reverse`
|
|
124
|
+
Reverses a string or array.
|
|
125
|
+
```html
|
|
126
|
+
{{ "hello"|reverse }} → "olleh"
|
|
127
|
+
{{ items|reverse }} → [3, 2, 1]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `split:separator`
|
|
131
|
+
Splits a string into an array. Default separator is a single space.
|
|
132
|
+
```html
|
|
133
|
+
{{ "a,b,c"|split:"," }} → ["a", "b", "c"]
|
|
134
|
+
{{ "hello world"|split }} → ["hello", "world"]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `replace:old,new`
|
|
138
|
+
Replaces occurrences of `old` with `new` in the string.
|
|
139
|
+
```html
|
|
140
|
+
{{ "hello world"|replace:"world,Earth" }} → "hello Earth"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### `length_is:N`
|
|
144
|
+
Returns `true` if the value's length equals N, otherwise `false`.
|
|
145
|
+
```html
|
|
146
|
+
{{ "hello"|length_is:5 }} → true
|
|
147
|
+
{{ items|length_is:3 }} → true/false
|
|
148
|
+
```
|
|
149
|
+
|
|
116
150
|
---
|
|
117
151
|
|
|
118
152
|
## HTML / Security Filters
|
|
@@ -131,12 +165,6 @@ Explicitly escapes HTML entities. Useful when `autoescape` is `off`.
|
|
|
131
165
|
{% endautoescape %}
|
|
132
166
|
```
|
|
133
167
|
|
|
134
|
-
### `escapejs`
|
|
135
|
-
Escapes characters for safe use inside JavaScript string literals.
|
|
136
|
-
```html
|
|
137
|
-
{{ 'Test "quotes" and \backs'|escapejs }}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
168
|
---
|
|
141
169
|
|
|
142
170
|
## URL / Encoding Filters
|
|
@@ -148,16 +176,23 @@ URL-encodes the string. By default, uses query-string encoding (spaces → `+`).
|
|
|
148
176
|
{{ "a/b c"|urlencode }} → "a%2Fb+c"
|
|
149
177
|
```
|
|
150
178
|
|
|
151
|
-
|
|
179
|
+
### `escapeuri`
|
|
180
|
+
Percent-encodes all special characters in a URI.
|
|
152
181
|
```html
|
|
153
|
-
{{ "
|
|
182
|
+
{{ "https://example.com?q=hello world"|escapeuri }}
|
|
183
|
+
→ "https%3A%2F%2Fexample.com%3Fq%3Dhello%20world"
|
|
154
184
|
```
|
|
155
185
|
|
|
156
|
-
### `
|
|
157
|
-
|
|
186
|
+
### `base64_encode`
|
|
187
|
+
Encodes a string to Base64.
|
|
158
188
|
```html
|
|
159
|
-
{{ "
|
|
160
|
-
|
|
189
|
+
{{ "hello"|base64_encode }} → "aGVsbG8="
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### `base64_decode`
|
|
193
|
+
Decodes a Base64 string. Returns the original value if decoding fails or produces invalid UTF-8.
|
|
194
|
+
```html
|
|
195
|
+
{{ "aGVsbG8="|base64_decode }} → "hello"
|
|
161
196
|
```
|
|
162
197
|
|
|
163
198
|
---
|
|
@@ -168,21 +203,15 @@ Percent-encodes all special characters in a URL.
|
|
|
168
203
|
Formats the value using Python-style format strings (`%s`, `%d`, etc.).
|
|
169
204
|
```html
|
|
170
205
|
{{ 42|stringformat:"d" }} → "42"
|
|
171
|
-
{{ 3.14159|stringformat:"2f" }} → "3.14"
|
|
206
|
+
{{ 3.14159|stringformat:".2f" }} → "3.14"
|
|
172
207
|
{{ "x"|stringformat:"s" }} → "x"
|
|
173
208
|
```
|
|
174
209
|
|
|
175
|
-
### `
|
|
176
|
-
|
|
210
|
+
### `json`
|
|
211
|
+
Safely serializes a value to JSON, marked safe for use inside `<script>` blocks.
|
|
177
212
|
```html
|
|
178
|
-
{{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
### `ljust:N` / `rjust:N`
|
|
182
|
-
Left/right-justifies the string in a field of width N.
|
|
183
|
-
```html
|
|
184
|
-
{{ "Hi"|ljust:10 }} → "Hi "
|
|
185
|
-
{{ "Hi"|rjust:10 }} → " Hi"
|
|
213
|
+
{{ data|json }}
|
|
214
|
+
<!-- Output: {"users":[{"name":"Alice"}]} (not HTML-escaped) -->
|
|
186
215
|
```
|
|
187
216
|
|
|
188
217
|
---
|
|
@@ -198,13 +227,6 @@ Returns the length of an array, object, string, or any object with a `.length` p
|
|
|
198
227
|
{{ undefined|length }} → 0
|
|
199
228
|
```
|
|
200
229
|
|
|
201
|
-
### `length_is:N`
|
|
202
|
-
Returns `true` if the value's length equals N, otherwise `false`.
|
|
203
|
-
```html
|
|
204
|
-
{{ "hello"|length_is:5 }} → true
|
|
205
|
-
{{ items|length_is:3 }} → true/false
|
|
206
|
-
```
|
|
207
|
-
|
|
208
230
|
### `join:separator`
|
|
209
231
|
Joins an array with the specified separator.
|
|
210
232
|
```html
|
|
@@ -221,16 +243,22 @@ Slices an array or string like Python (`[start:end]`). Supports negative indices
|
|
|
221
243
|
{{ "hello"|slice:"1:4" }} → "ell"
|
|
222
244
|
```
|
|
223
245
|
|
|
224
|
-
### `
|
|
225
|
-
|
|
246
|
+
### `sort`
|
|
247
|
+
Sorts an array in ascending order. Strings use locale-aware comparison.
|
|
226
248
|
```html
|
|
227
|
-
{{
|
|
249
|
+
{{ [3, 1, 2]|sort }} → [1, 2, 3]
|
|
228
250
|
```
|
|
229
251
|
|
|
230
|
-
### `
|
|
231
|
-
|
|
252
|
+
### `unique`
|
|
253
|
+
Removes duplicate values from an array.
|
|
232
254
|
```html
|
|
233
|
-
{{
|
|
255
|
+
{{ [1, 2, 2, 3]|unique }} → [1, 2, 3]
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### `random`
|
|
259
|
+
Returns a random element from an array.
|
|
260
|
+
```html
|
|
261
|
+
{{ ["a", "b", "c"]|random }} → "b" (random)
|
|
234
262
|
```
|
|
235
263
|
|
|
236
264
|
### `dictsort:"key"`
|
|
@@ -269,7 +297,7 @@ Uses the fallback value only if the original value is `null` or `undefined` (not
|
|
|
269
297
|
```
|
|
270
298
|
|
|
271
299
|
### `firstof`
|
|
272
|
-
Returns the first truthy value from the arguments
|
|
300
|
+
Returns the first truthy value from the arguments.
|
|
273
301
|
```html
|
|
274
302
|
{{ ""|firstof:user.name:guest:default }} → user.name or "guest" or "default"
|
|
275
303
|
```
|
|
@@ -294,7 +322,7 @@ Formats a date using Django-style format codes:
|
|
|
294
322
|
| `i` | Minutes | `00–59` |
|
|
295
323
|
| `s` | Seconds | `00–59` |
|
|
296
324
|
| `F` | Full month name | `January` |
|
|
297
|
-
| `M` | Short month name | `Jan` |
|
|
325
|
+
| `M` | Short day/month name | `Jan` |
|
|
298
326
|
|
|
299
327
|
```html
|
|
300
328
|
{{ post.published|date:"Y-m-d" }} → "2026-08-31"
|
|
@@ -328,15 +356,68 @@ Returns a human-readable "time until" string.
|
|
|
328
356
|
{{ event.date|timeuntil }} → "5 days"
|
|
329
357
|
```
|
|
330
358
|
|
|
359
|
+
### `time_diff:date`
|
|
360
|
+
Returns the absolute time difference between two dates as a human-readable string.
|
|
361
|
+
```html
|
|
362
|
+
{{ event.date|time_diff }} → "3 days"
|
|
363
|
+
{{ event.date|time_diff:now }} → difference from now
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### `ago`
|
|
367
|
+
Returns a human-readable relative time string like "2 days ago", "1 hour ago", etc.
|
|
368
|
+
```html
|
|
369
|
+
{{ comment.created|ago }} → "2 days ago"
|
|
370
|
+
{{ comment.created|ago }} → "just now" (if less than 1 minute ago)
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### `until`
|
|
374
|
+
Returns a human-readable string representing time until the given date.
|
|
375
|
+
```html
|
|
376
|
+
{{ event.date|until }} → "3 days"
|
|
377
|
+
{{ event.date|until }} → "now" (if less than 1 minute away)
|
|
378
|
+
```
|
|
379
|
+
|
|
331
380
|
---
|
|
332
381
|
|
|
333
382
|
## Numeric Filters
|
|
334
383
|
|
|
335
384
|
### `add:N`
|
|
336
|
-
Adds N to the value. Also works for string concatenation.
|
|
385
|
+
Adds N to the value. Also works for string concatenation and array concatenation.
|
|
337
386
|
```html
|
|
338
387
|
{{ count|add:5 }} → count + 5
|
|
339
|
-
{{ items|add:other }} →
|
|
388
|
+
{{ items|add:other }} → concatenated array or string
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### `sub:N`
|
|
392
|
+
Subtracts N from the value.
|
|
393
|
+
```html
|
|
394
|
+
{{ 10|sub:3 }} → 7
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### `mult:N`
|
|
398
|
+
Multiplies the value by N.
|
|
399
|
+
```html
|
|
400
|
+
{{ 4|mult:5 }} → 20
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### `square`
|
|
404
|
+
Returns the square of the value.
|
|
405
|
+
```html
|
|
406
|
+
{{ 6|square }} → 36
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### `sqrt`
|
|
410
|
+
Returns the square root of the value. Returns 0 for negative numbers.
|
|
411
|
+
```html
|
|
412
|
+
{{ 9|sqrt }} → 3
|
|
413
|
+
{{ -1|sqrt }} → 0
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### `mod:N`
|
|
417
|
+
Returns the modulo (remainder) of the value divided by N. Returns 0 if N is 0.
|
|
418
|
+
```html
|
|
419
|
+
{{ 10|mod:3 }} → 1
|
|
420
|
+
{{ 10|mod:0 }} → 0
|
|
340
421
|
```
|
|
341
422
|
|
|
342
423
|
### `divisibleby:N`
|
|
@@ -363,6 +444,61 @@ Formats a number to N decimal places. Django default is 1 decimal.
|
|
|
363
444
|
{{ 3.000|floatformat:0 }} → "3"
|
|
364
445
|
```
|
|
365
446
|
|
|
447
|
+
### `abs`
|
|
448
|
+
Returns the absolute value of a number.
|
|
449
|
+
```html
|
|
450
|
+
{{ -5|abs }} → 5
|
|
451
|
+
{{ 5|abs }} → 5
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### `round:N`
|
|
455
|
+
Rounds to N decimal places (default: 0 decimals).
|
|
456
|
+
```html
|
|
457
|
+
{{ 3.14159|round:2 }} → 3.14
|
|
458
|
+
{{ 3.5|round }} → 4
|
|
459
|
+
{{ 3.4|round }} → 3
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### `floor`
|
|
463
|
+
Rounds down to the nearest integer.
|
|
464
|
+
```html
|
|
465
|
+
{{ 3.7|floor }} → 3
|
|
466
|
+
{{ -3.7|floor }} → -4
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### `ceil`
|
|
470
|
+
Rounds up to the nearest integer.
|
|
471
|
+
```html
|
|
472
|
+
{{ 3.2|ceil }} → 4
|
|
473
|
+
{{ -3.2|ceil }} → -3
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### `min:N` / `min:array`
|
|
477
|
+
Returns the minimum of the value and N, or the minimum element in an array.
|
|
478
|
+
```html
|
|
479
|
+
{{ 5|min:2 }} → 2
|
|
480
|
+
{{ [5, 2, 8]|min }} → 2
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### `max:N` / `max:array`
|
|
484
|
+
Returns the maximum of the value and N, or the maximum element in an array.
|
|
485
|
+
```html
|
|
486
|
+
{{ 5|max:2 }} → 5
|
|
487
|
+
{{ [5, 2, 8]|max }} → 8
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### `sum`
|
|
491
|
+
Returns the sum of all elements in an array.
|
|
492
|
+
```html
|
|
493
|
+
{{ [1, 2, 3, 4]|sum }} → 10
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### `average`
|
|
497
|
+
Returns the arithmetic mean of an array. Returns 0 for empty arrays.
|
|
498
|
+
```html
|
|
499
|
+
{{ [1, 2, 3, 4]|average }} → 2.5
|
|
500
|
+
```
|
|
501
|
+
|
|
366
502
|
---
|
|
367
503
|
|
|
368
504
|
## Miscellaneous Filters
|
|
@@ -394,44 +530,111 @@ Formats a byte count as a human-readable file size.
|
|
|
394
530
|
|
|
395
531
|
---
|
|
396
532
|
|
|
397
|
-
##
|
|
533
|
+
## Data Formatting Filters
|
|
398
534
|
|
|
399
|
-
|
|
535
|
+
### `currency:symbol`
|
|
536
|
+
Formats a number as currency with thousands separators and 2 decimal places. Default symbol is `$`.
|
|
537
|
+
```html
|
|
538
|
+
{{ 1234.5|currency }} → "$1,234.50"
|
|
539
|
+
{{ 1234.5|currency:"€" }} → "€1,234.50"
|
|
540
|
+
```
|
|
400
541
|
|
|
401
|
-
|
|
402
|
-
|
|
542
|
+
### `phone_number`
|
|
543
|
+
Formats a 10-digit US phone number as `(123) 456-7890`. Handles 11-digit numbers with leading `1` as `+1 (123) 456-7890`.
|
|
544
|
+
```html
|
|
545
|
+
{{ "1234567890"|phone_number }} → "(123) 456-7890"
|
|
546
|
+
{{ "11234567890"|phone_number }} → "+1 (123) 456-7890"
|
|
547
|
+
```
|
|
403
548
|
|
|
404
|
-
|
|
405
|
-
|
|
549
|
+
### `email`
|
|
550
|
+
Wraps an email address in a `mailto:` link.
|
|
551
|
+
```html
|
|
552
|
+
{{ "user@example.com"|email }} → "mailto:user@example.com"
|
|
553
|
+
```
|
|
406
554
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
555
|
+
### `url`
|
|
556
|
+
Ensures a URL has a protocol prefix. Prepends `https://` if missing.
|
|
557
|
+
```html
|
|
558
|
+
{{ "example.com"|url }} → "https://example.com"
|
|
559
|
+
{{ "https://example.com"|url }} → "https://example.com"
|
|
560
|
+
```
|
|
413
561
|
|
|
414
|
-
|
|
415
|
-
|
|
562
|
+
### `mask:char`
|
|
563
|
+
Masks all but the last 4 characters of a string. Default mask character is `*`.
|
|
564
|
+
```html
|
|
565
|
+
{{ "1234567890"|mask }} → "******7890"
|
|
566
|
+
{{ "1234567890"|mask:"#" }} → "######7890"
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### `whatsapp_link:message`
|
|
570
|
+
Generates a WhatsApp link (`https://wa.me/NUMBER`) with an optional pre-filled message.
|
|
571
|
+
```html
|
|
572
|
+
{{ "1234567890"|whatsapp_link }} → "https://wa.me/1234567890"
|
|
573
|
+
{{ "1234567890"|whatsapp_link:"Hello" }} → "https://wa.me/1234567890?text=Hello"
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### `credit_card`
|
|
577
|
+
Formats a credit card number with dashes every 4 digits.
|
|
578
|
+
```html
|
|
579
|
+
{{ "4111111111111111"|credit_card }} → "4111-1111-1111-1111"
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### `ssn`
|
|
583
|
+
Formats a 9-digit Social Security Number as `XXX-XX-XXXX`.
|
|
584
|
+
```html
|
|
585
|
+
{{ "123456789"|ssn }} → "123-45-6789"
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### `ip_address`
|
|
589
|
+
Formats a 10 or 12 digit string as a dotted IP address.
|
|
590
|
+
```html
|
|
591
|
+
{{ "192168011001"|ip_address }} → "192.168.11.001"
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### `uuid`
|
|
595
|
+
Generates a random UUID v4 string.
|
|
596
|
+
```html
|
|
597
|
+
{{ ""|uuid }} → "9787a126-cb81-4825-b63b-73345a51a1c1"
|
|
416
598
|
```
|
|
417
599
|
|
|
418
600
|
---
|
|
419
601
|
|
|
420
|
-
##
|
|
602
|
+
## Humanize Filters (Built-in Library)
|
|
421
603
|
|
|
422
|
-
|
|
604
|
+
These filters are part of the `humanize` library, activated by default or via `{% load humanize %}`.
|
|
423
605
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
| Number | Integer literal | `{{ value|truncatewords:10 }}` |
|
|
606
|
+
### `intcomma`
|
|
607
|
+
Adds commas to thousands places.
|
|
608
|
+
```html
|
|
609
|
+
{{ 1234567|intcomma }} → "1,234,567"
|
|
610
|
+
```
|
|
430
611
|
|
|
612
|
+
### `intword`
|
|
613
|
+
Converts large numbers to human-friendly strings.
|
|
431
614
|
```html
|
|
432
|
-
{{
|
|
433
|
-
{{
|
|
434
|
-
|
|
615
|
+
{{ 1234567|intword }} → "1.2 million"
|
|
616
|
+
{{ 1000|intword }} → "1.0 thousand"
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### `apnumber`
|
|
620
|
+
Converts numbers to their word equivalent (0–19).
|
|
621
|
+
```html
|
|
622
|
+
{{ 3|apnumber }} → "three"
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### `ordinal`
|
|
626
|
+
Returns the ordinal suffix for a number.
|
|
627
|
+
```html
|
|
628
|
+
{{ 1|ordinal }} → "1st"
|
|
629
|
+
{{ 2|ordinal }} → "2nd"
|
|
630
|
+
{{ 11|ordinal }} → "11th"
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### `naturalday`
|
|
634
|
+
Returns "today", "yesterday", "tomorrow", or the date string for other dates.
|
|
635
|
+
```html
|
|
636
|
+
{{ today|naturalday }} → "today"
|
|
637
|
+
{{ yesterday|naturalday }} → "yesterday"
|
|
435
638
|
```
|
|
436
639
|
|
|
437
640
|
---
|
|
@@ -464,77 +667,42 @@ Group an array of objects by a common attribute. Returns an array of `{ grouper,
|
|
|
464
667
|
|
|
465
668
|
---
|
|
466
669
|
|
|
467
|
-
##
|
|
468
|
-
|
|
469
|
-
### `stringformat:"fmt"`
|
|
470
|
-
Format a value using Python-style format strings (`%s`, `%d`, `%.2f`, `%x`, etc.).
|
|
471
|
-
|
|
472
|
-
```html
|
|
473
|
-
{{ 42|stringformat:"d" }} → "42"
|
|
474
|
-
{{ 3.14159|stringformat:".2f" }} → "3.14"
|
|
475
|
-
{{ "hello"|stringformat:"s" }} → "hello"
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
---
|
|
670
|
+
## Custom Filters
|
|
479
671
|
|
|
480
|
-
|
|
672
|
+
Register custom filters with `registerFilter`:
|
|
481
673
|
|
|
482
|
-
|
|
483
|
-
|
|
674
|
+
```javascript
|
|
675
|
+
const { registerFilter } = require('miki-template');
|
|
484
676
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
{{ "a/b c"|urlencode }} → "a%2Fb+c"
|
|
488
|
-
```
|
|
677
|
+
// Simple filter
|
|
678
|
+
registerFilter('reverse', (val) => String(val).split('').reverse().join(''));
|
|
489
679
|
|
|
490
|
-
|
|
491
|
-
|
|
680
|
+
// Filter with argument
|
|
681
|
+
registerFilter('truncate', (val, length) => {
|
|
682
|
+
const str = String(val);
|
|
683
|
+
if (str.length <= length) return str;
|
|
684
|
+
return str.slice(0, length) + '...';
|
|
685
|
+
});
|
|
492
686
|
|
|
493
|
-
|
|
494
|
-
{{
|
|
687
|
+
// Chaining works automatically:
|
|
688
|
+
// {{ name|reverse|truncate:5 }}
|
|
495
689
|
```
|
|
496
690
|
|
|
497
691
|
---
|
|
498
692
|
|
|
499
|
-
##
|
|
500
|
-
|
|
501
|
-
### `cut:value`
|
|
502
|
-
Remove all occurrences of a substring.
|
|
503
|
-
|
|
504
|
-
```html
|
|
505
|
-
{{ "hello hello"|cut:" " }} → "hellohello"
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
### `addslashes`
|
|
509
|
-
Add backslashes before quotes and backslashes.
|
|
510
|
-
|
|
511
|
-
```html
|
|
512
|
-
{{ 'He said "Hi"|addslashes }} → He said \"Hi\"
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
### `removetags:tag1,tag2,...`
|
|
516
|
-
Remove named HTML tags and their contents.
|
|
517
|
-
|
|
518
|
-
```html
|
|
519
|
-
{{ "<p>Hello</p><b>World</b>"|removetags:"p,b" }} → "HelloWorld"
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
### `length_is:N`
|
|
523
|
-
Return `true` if the value's length equals N.
|
|
524
|
-
|
|
525
|
-
```html
|
|
526
|
-
{{ "hello"|length_is:5 }} → true
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
---
|
|
693
|
+
## Filter Argument Types
|
|
530
694
|
|
|
531
|
-
|
|
695
|
+
Filters accept the following argument types:
|
|
532
696
|
|
|
533
|
-
|
|
534
|
-
|
|
697
|
+
| Syntax | Type | Example |
|
|
698
|
+
|--------|------|---------|
|
|
699
|
+
| Unquoted | Variable lookup | `{{ value|filter:count }}` |
|
|
700
|
+
| Double-quoted | String literal | `{{ value|filter:"hello" }}` |
|
|
701
|
+
| Single-quoted | String literal | `{{ value|filter:'world' }}` |
|
|
702
|
+
| Number | Integer literal | `{{ value|truncatewords:10 }}` |
|
|
535
703
|
|
|
536
704
|
```html
|
|
537
|
-
{{
|
|
538
|
-
{{
|
|
539
|
-
{{
|
|
705
|
+
{{ user.name|default:"Guest" }} <!-- String default -->
|
|
706
|
+
{{ items|slice:"1:3" }} <!-- Slice notation -->
|
|
707
|
+
{{ price|floatformat:2 }} <!-- Decimal places -->
|
|
540
708
|
```
|
package/docs/partialdef.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Partial Definition (`partialdef`)
|
|
2
2
|
|
|
3
|
-
`partialdef` is the cornerstone feature that brings Django‑style **named template fragments** to Node.js. It allows you to define a reusable block once and render it multiple times, optionally **inline** for immediate output.
|
|
3
|
+
`partialdef` is the cornerstone feature that brings Django‑style **named template fragments** to Node.js. It allows you to define a reusable block once and render it multiple times, optionally **inline** for immediate output. Combined with the `setupExpress` helper, partials can be served as standalone HTTP responses for HTMX-style UIs.
|
|
4
4
|
|
|
5
5
|
## Syntax
|
|
6
6
|
```html
|
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
```
|
|
18
18
|
The engine looks up the definition in the current rendering **Context** (`context.partialDefs`) and injects the rendered output.
|
|
19
19
|
|
|
20
|
+
## Including a Partial From Another File
|
|
21
|
+
Use the `file#partial` syntax to include just a single named partial:
|
|
22
|
+
```html
|
|
23
|
+
{% include "home.html#card" with title="Hi" %}
|
|
24
|
+
```
|
|
25
|
+
|
|
20
26
|
## API Usage
|
|
21
27
|
```js
|
|
22
28
|
const tpl = `{% partialdef api %}API {{ data }}{% endpartialdef %}`;
|
|
@@ -24,11 +30,34 @@ const compiled = compile(tpl);
|
|
|
24
30
|
const out = compiled.renderPartial('api', { data: 123 }); // "API 123"
|
|
25
31
|
```
|
|
26
32
|
|
|
33
|
+
`renderPartialFromSource` and `renderPartialFromFile` are also exported at the top level:
|
|
34
|
+
```js
|
|
35
|
+
const miki = require('miki-template');
|
|
36
|
+
miki.renderPartialFromFile('views/home.html', 'card', { user: req.user });
|
|
37
|
+
miki.renderPartialFromSource(src, 'card', { user: req.user }, { views: 'views' });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Serving a Partial Over HTTP (HTMX)
|
|
41
|
+
With `miki.setupExpress(app, { extension: 'html', views: './views' })`, the same `res.render(...)` call you use for full pages also serves a single partial by appending `#partialName` to the view name:
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
app.get('/partials/:name', (req, res) =>
|
|
45
|
+
res.render(`home#${req.params.name}`, { user: req.user })
|
|
46
|
+
);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The same effect can be obtained via the lighter `expressPartialRenderer()` middleware:
|
|
50
|
+
```javascript
|
|
51
|
+
app.use(miki.expressPartialRenderer());
|
|
52
|
+
app.get('/card', (req, res) => res.renderPartial('home#card', { user: req.user }));
|
|
53
|
+
```
|
|
54
|
+
|
|
27
55
|
## Features
|
|
28
56
|
- **Full tag parity** – conditionals (`if`), loops (`for`), variable scoping (`with`) work inside a `partialdef`.
|
|
29
57
|
- **Nested partials** – you can define a partial inside another; inner definitions are registered first and can be used by the outer.
|
|
30
58
|
- **Scope isolation** – each rendering of a partial receives its own scope, mirroring Django’s behavior.
|
|
31
59
|
- **Inline rendering** – render inline without an extra `{% partial %}` tag (`{% partialdef foo inline %}…{% endpartialdef %}`).
|
|
60
|
+
- **`with` arguments** – bind extra context values when rendering: `{% partial card with title="Hello" description="World" %}`.
|
|
32
61
|
- **Performance** – partials are compiled once per template; subsequent renders reuse the compiled AST.
|
|
33
62
|
|
|
34
63
|
## Common Pitfalls
|