miki-template 1.2.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.
Files changed (73) hide show
  1. package/.github/workflows/ci.yml +54 -0
  2. package/AGENT.md +71 -0
  3. package/API_REFERENCE.md +314 -0
  4. package/CHANGELOG.md +97 -0
  5. package/CODE_OF_CONDUCT.md +14 -0
  6. package/CONTRIBUTING.md +27 -0
  7. package/README.md +304 -0
  8. package/ROADMAP.md +40 -0
  9. package/benchmarks/report.json +17 -0
  10. package/benchmarks/run.js +49 -0
  11. package/benchmarks/templates/large.dtpl +7 -0
  12. package/benchmarks/templates/medium.dtpl +3 -0
  13. package/benchmarks/templates/small.dtpl +7 -0
  14. package/context/component.md +109 -0
  15. package/context/prd.md +131 -0
  16. package/context/project-structure.md +33 -0
  17. package/docs/README.md +18 -0
  18. package/docs/advanced_usage.md +71 -0
  19. package/docs/api.md +102 -0
  20. package/docs/filters.md +540 -0
  21. package/docs/installation.md +106 -0
  22. package/docs/overview.md +57 -0
  23. package/docs/partialdef.md +41 -0
  24. package/docs/security.md +27 -0
  25. package/docs/tags.md +610 -0
  26. package/docs/usage.md +599 -0
  27. package/eslint.config.mjs +34 -0
  28. package/miki-template-1.2.0.vsix +0 -0
  29. package/miki-template-extension/LICENSE +21 -0
  30. package/miki-template-extension/README.md +82 -0
  31. package/miki-template-extension/icon.png +0 -0
  32. package/miki-template-extension/icon.svg +10 -0
  33. package/miki-template-extension/package.json +46 -0
  34. package/miki-template-extension/snippets/miki-template.json +177 -0
  35. package/miki-template-extension/syntaxes/language-configuration.json +26 -0
  36. package/miki-template-extension/syntaxes/miki-template.tmLanguage.json +146 -0
  37. package/package.json +31 -0
  38. package/snippets/miki-template.json +177 -0
  39. package/src/asyncRender.js +21 -0
  40. package/src/cache.js +41 -0
  41. package/src/context.js +122 -0
  42. package/src/context_processors.js +41 -0
  43. package/src/esm.mjs +72 -0
  44. package/src/filters.js +527 -0
  45. package/src/i18n.js +171 -0
  46. package/src/index.js +454 -0
  47. package/src/lexer.js +92 -0
  48. package/src/libraries.js +240 -0
  49. package/src/parser.js +250 -0
  50. package/src/security.js +51 -0
  51. package/src/tags/control.js +591 -0
  52. package/src/tags/helpers.js +27 -0
  53. package/src/tags/i18n.js +230 -0
  54. package/src/tags/inheritance.js +216 -0
  55. package/src/tags/registry.js +18 -0
  56. package/src/tags/util.js +322 -0
  57. package/src/types.d.ts +107 -0
  58. package/syntaxes/language-configuration.json +26 -0
  59. package/syntaxes/miki-template.tmLanguage.json +146 -0
  60. package/tests/asyncRender.test.js +17 -0
  61. package/tests/base.html +6 -0
  62. package/tests/child.html +3 -0
  63. package/tests/context_processors.test.js +13 -0
  64. package/tests/esm.test.mjs +26 -0
  65. package/tests/filters.test.js +99 -0
  66. package/tests/include_security.test.js +9 -0
  67. package/tests/lexer.test.js +45 -0
  68. package/tests/parser.test.js +55 -0
  69. package/tests/partial.html +1 -0
  70. package/tests/partialdef.test.js +40 -0
  71. package/tests/production_checks.js +57 -0
  72. package/tests/security.test.js +28 -0
  73. package/tests/tags.test.js +203 -0
@@ -0,0 +1,41 @@
1
+ # Partial Definition (`partialdef`)
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.
4
+
5
+ ## Syntax
6
+ ```html
7
+ {% partialdef name [inline] %}
8
+ ...template code...
9
+ {% endpartialdef %}
10
+ ```
11
+ - `name` – identifier used with `{% partial name %}`.
12
+ - Optional `inline` – if present, the block is rendered **where it is defined**; no separate `{% partial %}` call is required.
13
+
14
+ ## Rendering a Partial
15
+ ```html
16
+ {% partial greeting %}
17
+ ```
18
+ The engine looks up the definition in the current rendering **Context** (`context.partialDefs`) and injects the rendered output.
19
+
20
+ ## API Usage
21
+ ```js
22
+ const tpl = `{% partialdef api %}API {{ data }}{% endpartialdef %}`;
23
+ const compiled = compile(tpl);
24
+ const out = compiled.renderPartial('api', { data: 123 }); // "API 123"
25
+ ```
26
+
27
+ ## Features
28
+ - **Full tag parity** – conditionals (`if`), loops (`for`), variable scoping (`with`) work inside a `partialdef`.
29
+ - **Nested partials** – you can define a partial inside another; inner definitions are registered first and can be used by the outer.
30
+ - **Scope isolation** – each rendering of a partial receives its own scope, mirroring Django’s behavior.
31
+ - **Inline rendering** – render inline without an extra `{% partial %}` tag (`{% partialdef foo inline %}…{% endpartialdef %}`).
32
+ - **Performance** – partials are compiled once per template; subsequent renders reuse the compiled AST.
33
+
34
+ ## Common Pitfalls
35
+ | Issue | Symptom | Fix |
36
+ |-------|---------|-----|
37
+ | Missing partial name | `{% partial %}` renders nothing | Ensure the name matches a defined `partialdef`. |
38
+ | Variable not found | Appears empty | Variables are resolved in the current context; use `{% with %}` inside the partial if you need a local alias. |
39
+ | Inline vs non‑inline confusion | Duplicate output | Use `inline` only when you want immediate rendering. |
40
+
41
+ > Implementation lives in `src/tags/control.js` (class `PartialDefNode` and `PartialNode`).
@@ -0,0 +1,27 @@
1
+ # Security
2
+
3
+ `miki-template` is built with **secure defaults**. All variables are **auto‑escaped** unless explicitly marked safe.
4
+
5
+ ## Auto‑escaping
6
+ - Every string output goes through `escapeHtml` before being concatenated.
7
+ - Use the `|safe` filter or `markSafe(value)` to bypass escaping when you trust the data.
8
+
9
+ ## CSRF Protection
10
+ - The `{% csrf_token %}` tag renders a hidden `<input>` containing the `csrf_token` value from the rendering context.
11
+ - Example:
12
+ ```html
13
+ <form method="post">{% csrf_token %} ... </form>
14
+ ```
15
+ - It is a thin wrapper; you must generate and store `csrf_token` in your Express middleware.
16
+
17
+ ## CSP Nonce
18
+ - `{% csp_nonce_attr %}` injects `nonce="{{ csp_nonce }}"` when `csp_nonce` is present in the context.
19
+ - Useful for inline scripts when you have a CSP policy with `script-src 'nonce-...';`.
20
+
21
+ ## SafeString Wrapper
22
+ - Filters returning `SafeString` bypass auto‑escaping. The wrapper is applied automatically by the `safe` filter.
23
+
24
+ ## No `eval`
25
+ - Template expressions are parsed into an AST and evaluated using a sandboxed evaluator that **never calls `eval` or `new Function`**.
26
+
27
+ > Security‑related code lives in `src/security.js` and the tag implementations in `src/tags/control.js`.
package/docs/tags.md ADDED
@@ -0,0 +1,610 @@
1
+ # Tags Reference
2
+
3
+ This document provides a detailed reference for every built-in block tag in **miki-template**, grouped by function.
4
+
5
+ ---
6
+
7
+ ## Control Flow Tags
8
+
9
+ ### `{% if %} / {% elif %} / {% else %} / {% endif %}`
10
+
11
+ Conditionally renders content based on an expression.
12
+
13
+ ```html
14
+ {% if user.is_authenticated %}
15
+ <p>Hello, {{ user.name }}!</p>
16
+ {% elif user.is_guest %}
17
+ <p>Welcome, guest!</p>
18
+ {% else %}
19
+ <p>Please log in.</p>
20
+ {% endif %}
21
+ ```
22
+
23
+ **Supported operators:**
24
+
25
+ | Operator | Meaning |
26
+ |----------|---------|
27
+ | `==` | Equal |
28
+ | `!=` | Not equal |
29
+ | `<` | Less than |
30
+ | `<=` | Less than or equal |
31
+ | `>` | Greater than |
32
+ | `>=` | Greater than or equal |
33
+ | `in` | Membership (item in list) |
34
+ | `not in` | Non-membership |
35
+ | `and` | Logical AND |
36
+ | `or` | Logical OR |
37
+ | `not` | Logical NOT |
38
+
39
+ **Operator precedence** (highest to lowest): comparison → `and` → `or`
40
+
41
+ ```html
42
+ {% if user.age >= 18 and user.is_verified %}
43
+ <p>Eligible to vote.</p>
44
+ {% endif %}
45
+
46
+ {% if item not in cart %}
47
+ <button>Add to cart</button>
48
+ {% endif %}
49
+ ```
50
+
51
+ ---
52
+
53
+ ### `{% for %} / {% empty %} / {% endfor %}`
54
+
55
+ Iterates over arrays or objects.
56
+
57
+ ```html
58
+ <ul>
59
+ {% for user in users %}
60
+ <li>{{ user.name }}</li>
61
+ {% empty %}
62
+ <li>No users found.</li>
63
+ {% endfor %}
64
+ </ul>
65
+ ```
66
+
67
+ **Object iteration** — unpacks key and value:
68
+
69
+ ```html
70
+ {% for key, value in config %}
71
+ <dt>{{ key }}</dt>
72
+ <dd>{{ value }}</dd>
73
+ {% endfor %}
74
+ ```
75
+
76
+ **Tuple unpacking** — unpacks index and item:
77
+
78
+ ```html
79
+ {% for item, index in items %}
80
+ {{ forloop.counter }}. {{ item }}
81
+ {% endfor %}
82
+ ```
83
+
84
+ **Loop metadata** — `forloop` object is available inside the loop:
85
+
86
+ | Property | Type | Description |
87
+ |----------|------|-------------|
88
+ | `forloop.counter` | integer | Current iteration (1-indexed) |
89
+ | `forloop.counter0` | integer | Current iteration (0-indexed) |
90
+ | `forloop.revcounter` | integer | Iterations remaining (counting down from 1) |
91
+ | `forloop.revcounter0` | integer | Iterations remaining (counting down from 0) |
92
+ | `forloop.first` | boolean | True on first iteration |
93
+ | `forloop.last` | boolean | True on last iteration |
94
+ | `forloop.parentloop` | object | Reference to parent loop's `forloop` |
95
+
96
+ **Nested loops example:**
97
+
98
+ ```html
99
+ {% for category in categories %}
100
+ <h2>{{ category.name }}</h2>
101
+ {% for product in category.products %}
102
+ {# forloop.counter = position in category #}
103
+ {# forloop.parentloop.counter = position in categories #}
104
+ <p>{{ forloop.parentloop.counter }}.{{ forloop.counter }}: {{ product }}</p>
105
+ {% endfor %}
106
+ {% endfor %}
107
+ ```
108
+
109
+ ---
110
+
111
+ ### `{% with %} / {% endwith %}`
112
+
113
+ Creates scoped aliases for variables or expressions.
114
+
115
+ ```html
116
+ {% with user.profile as profile %}
117
+ <img src="{{ profile.avatar }}">
118
+ <a href="{{ profile.url }}">{{ profile.display_name }}</a>
119
+ {% endwith %}
120
+ ```
121
+
122
+ **Multiple assignments** (Django-style):
123
+
124
+ ```html
125
+ {% with a=1 b=items.0.name c="static" %}
126
+ {{ a }} | {{ b }} | {{ c }}
127
+ {% endwith %}
128
+ ```
129
+
130
+ You can also unpack tuple-like values:
131
+
132
+ ```html
133
+ {% with key, value in item %}
134
+ <li>{{ key }}: {{ value }}</li>
135
+ {% endwith %}
136
+ ```
137
+
138
+ ---
139
+
140
+ ### `{% cycle %}`
141
+
142
+ Outputs one of its arguments for each iteration of a loop.
143
+
144
+ ```html
145
+ {% for row in rows %}
146
+ <tr class="{% cycle 'row-even' 'row-odd' %}">
147
+ <td>{{ row.name }}</td>
148
+ </tr>
149
+ {% endfor %}
150
+ ```
151
+
152
+ Named cycle for resumable state:
153
+
154
+ ```html
155
+ {% for item in items %}
156
+ {% cycle 'a' 'b' 'c' as marker silent %}
157
+ {% if marker == 'b' %}
158
+ <strong>{{ item }}</strong>
159
+ {% else %}
160
+ {{ item }}
161
+ {% endif %}
162
+ {% endfor %}
163
+ ```
164
+
165
+ ---
166
+
167
+ ### `{% firstof %}`
168
+
169
+ Outputs the first argument that evaluates to `true`.
170
+
171
+ ```html
172
+ {% firstof user.display_name user.username "Anonymous" %}
173
+ ```
174
+
175
+ With `{% else %}` for a fallback:
176
+
177
+ ```html
178
+ {% firstof user.display_name user.username %}
179
+ {{ firstof_output }}
180
+ {% else %}
181
+ Anonymous
182
+ {% endif %}
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Template Inheritance Tags
188
+
189
+ ### `{% extends %}`
190
+
191
+ Must be the first tag in a child template. Specifies the parent template.
192
+
193
+ ```html
194
+ {% extends "base.html" %}
195
+ ```
196
+
197
+ Can use expressions for dynamic parent (e.g., mobile vs desktop):
198
+
199
+ ```html
200
+ {% extends device|default:"base.html" %}
201
+ ```
202
+
203
+ **Security:** Path traversal is blocked — the template name must resolve within the configured `views` directories.
204
+
205
+ ---
206
+
207
+ ### `{% block %} / {% endblock %}`
208
+
209
+ Defines a replaceable section that child templates can override.
210
+
211
+ ```html
212
+ <!-- base.html -->
213
+ {% block content %}
214
+ Default content
215
+ {% endblock %}
216
+ ```
217
+
218
+ ```html
219
+ <!-- child.html -->
220
+ {% extends "base.html" %}
221
+ {% block content %}
222
+ Overridden content
223
+ {% endblock %}
224
+ ```
225
+
226
+ **`{{ block.super }}`** — renders the parent template's block content within an override:
227
+
228
+ ```html
229
+ {% block content %}
230
+ {{ block.super }}
231
+ <p>Additional content from child</p>
232
+ {% endblock %}
233
+ ```
234
+
235
+ ---
236
+
237
+ ### `{% block.super %}`
238
+
239
+ A special variable, not a tag. When used inside a `{% block %}`, it renders the parent template's version of that block.
240
+
241
+ ---
242
+
243
+ ## Include and Partial Tags
244
+
245
+ ### `{% include %}`
246
+
247
+ Includes another template file at render time. The included template gets a copy of the current context.
248
+
249
+ ```html
250
+ {% include "header.html" %}
251
+ {% include "sidebar.html" with active_section="home" %}
252
+ {% include "footer.html" without context %}
253
+ ```
254
+
255
+ **Security:** Path traversal is blocked to prevent reading arbitrary files outside the views directory.
256
+
257
+ ---
258
+
259
+ ### `{% partialdef %} / {% endpartialdef %}`
260
+
261
+ Defines a reusable fragment that can be rendered later via `{% partial %}`.
262
+
263
+ ```html
264
+ {% partialdef card %}
265
+ <div class="card">
266
+ <h3>{{ title }}</h3>
267
+ <p>{{ description }}</p>
268
+ </div>
269
+ {% endpartialdef %}
270
+
271
+ {% partial card with title="Hello" description="World" %}
272
+ ```
273
+
274
+ **Options:**
275
+
276
+ | Option | Description |
277
+ |--------|-------------|
278
+ | `inline` | Renders the definition inline at its location during parse. |
279
+ | `lazy` | Defers parsing until first use (default is eager parsing). |
280
+
281
+ **Programmatic access:**
282
+
283
+ ```javascript
284
+ const compiled = compile(template);
285
+ compiled.renderPartial('card', { title: 'Hi', description: 'There' });
286
+ ```
287
+
288
+ ---
289
+
290
+ ### `{% partial %}`
291
+
292
+ Renders a previously defined partial.
293
+
294
+ ```html
295
+ {% partial card %}
296
+ {% partial card with title="Custom" %}
297
+ ```
298
+
299
+ Supports passing context variables:
300
+
301
+ ```html
302
+ {% partial greeting with name=user.name %}
303
+ ```
304
+
305
+ ---
306
+
307
+ ## Utility Tags
308
+
309
+ ### `{% comment %} / {% endcomment %}`
310
+
311
+ Block comment that is stripped from the output entirely.
312
+
313
+ ```html
314
+ {% comment %}
315
+ This section is deprecated.
316
+ It will be removed in the next release.
317
+ {% endcomment %}
318
+ ```
319
+
320
+ Short form (single tag, self-closing):
321
+
322
+ ```html
323
+ {% comment %} This will not appear in output {% endcomment %}
324
+ ```
325
+
326
+ ---
327
+
328
+ ### `{% verbatim %} / {% endverbatim %}`
329
+
330
+ Prevents all tag/variable parsing inside the block.
331
+
332
+ ```html
333
+ {% verbatim %}
334
+ {{ this_is_not_a_variable }}
335
+ {% if this_is_not_a_tag %}Ignored{% endif %}
336
+ {% endverbatim %}
337
+ ```
338
+
339
+ ---
340
+
341
+ ### `{% load %}`
342
+
343
+ Loads additional filter libraries (for future extensibility).
344
+
345
+ ```html
346
+ {% load i18n %}
347
+ {% load custom_filters %}
348
+ ```
349
+
350
+ ---
351
+
352
+ ### `{% spaceless %} / {% endspaceless %}`
353
+
354
+ Removes whitespace between HTML tags.
355
+
356
+ ```html
357
+ {% spaceless %}
358
+ <div> <p>Hello</p> </div>
359
+ {% endspaceless %}
360
+ <!-- → <div><p>Hello</p></div> -->
361
+ ```
362
+
363
+ ---
364
+
365
+ ### `{% static %}`
366
+
367
+ Generates the URL for a static asset.
368
+
369
+ ```html
370
+ <img src="{% static "css/app.css" %}">
371
+ <script src="{% static "js/bundle.js" %}"></script>
372
+ ```
373
+
374
+ Configure the prefix:
375
+ ```javascript
376
+ compile(template, { staticUrl: '/assets/' });
377
+ ```
378
+
379
+ ---
380
+
381
+ ### `{% url %}`
382
+
383
+ Generates a URL for a named route using the provided `urlHelper` function.
384
+
385
+ ```html
386
+ <a href="{% url "home" %}">Home</a>
387
+ <a href="{% url "user-profile" user.id %}">Profile</a>
388
+ <a href="{% url "search" query=search_query %}">Search</a>
389
+ ```
390
+
391
+ Configure:
392
+ ```javascript
393
+ compile(template, {
394
+ urlHelper: (name, params, kwargs) => {
395
+ // return resolved URL string
396
+ }
397
+ });
398
+ ```
399
+
400
+ ---
401
+
402
+ ### `{% csrf_token %}`
403
+
404
+ Outputs a CSRF token hidden input for forms.
405
+
406
+ ```html
407
+ <form method="post">
408
+ {% csrf_token %}
409
+ <input type="text" name="title">
410
+ <button type="submit">Submit</button>
411
+ </form>
412
+ ```
413
+
414
+ Provide `csrf_token` in context:
415
+ ```javascript
416
+ res.render('form', { csrf_token: req.csrfToken() });
417
+ ```
418
+
419
+ ---
420
+
421
+ ### `{% csp_nonce_attr %}`
422
+
423
+ Outputs a `nonce="..."` attribute for Content Security Policy.
424
+
425
+ ```html
426
+ <script {% csp_nonce_attr %}>
427
+ console.log('CSP nonce');
428
+ </script>
429
+ ```
430
+
431
+ Provide `csp_nonce` in context:
432
+ ```javascript
433
+ res.render('page', { csp_nonce: req.nonce });
434
+ ```
435
+
436
+ ---
437
+
438
+ ### `{% regroup %}`
439
+
440
+ Regroups a list by a common attribute.
441
+
442
+ ```html
443
+ {% regroup users by department as departments %}
444
+ {% for dept in departments %}
445
+ <h3>{{ dept.grouper }}</h3>
446
+ {% for user in dept.list %}
447
+ <p>{{ user.name }}</p>
448
+ {% endfor %}
449
+ {% endfor %}
450
+ ```
451
+
452
+ ---
453
+
454
+ ### `{% trans "key" %}`
455
+
456
+ Outputs a translated string from the i18n registry.
457
+
458
+ ```html
459
+ {% trans "Hello, World!" %}
460
+ ```
461
+
462
+ With arguments:
463
+
464
+ ```html
465
+ {% trans "Hello, %s!" name=user.name %}
466
+ ```
467
+
468
+ With context:
469
+
470
+ ```html
471
+ {% trans context "verb" "He runs" %}
472
+ ```
473
+
474
+ ---
475
+
476
+ ### `{% blocktrans %}...{% endblocktrans %}`
477
+
478
+ Translates a block of text. Supports `{% with name=value %}` and `{% plural count name=value %}`.
479
+
480
+ ```html
481
+ {% blocktrans with name=user.name count items|length %}
482
+ {{ name }} has {{ items|length }} item.
483
+ {% plural %}
484
+ {{ name }} has {{ items|length }} items.
485
+ {% endblocktrans %}
486
+ ```
487
+
488
+ ---
489
+
490
+ ### `{% language "xx" %}...{% endlanguage %}`
491
+
492
+ Switches the active language for the enclosed block.
493
+
494
+ ```html
495
+ {% language "fr" %}
496
+ {% trans "Welcome" %}
497
+ {% endlanguage %}
498
+ ```
499
+
500
+ ---
501
+
502
+ ### `{% widthratio value max max_width %}`
503
+
504
+ Calculates a proportional width, commonly used for bar charts or progress indicators.
505
+
506
+ ```html
507
+ {% widthratio 25 100 150 %} <!-- → 37 (floor of 25/100*150) -->
508
+ ```
509
+
510
+ ---
511
+
512
+ ### `{% debug %}`
513
+
514
+ Dumps the current template context as a formatted HTML `<pre>` block. Useful during development.
515
+
516
+ ```html
517
+ <pre>
518
+ {% debug %}
519
+ </pre>
520
+ ```
521
+
522
+ ---
523
+
524
+ ### `{% load library1 library2 %}`
525
+
526
+ Loads one or more plugin libraries, making their tags, filters, and helpers available.
527
+
528
+ ```html
529
+ {% load i18n humanize cache %}
530
+ ```
531
+
532
+ Built-in libraries:
533
+ - `i18n` — `trans`, `blocktrans`, `language`
534
+ - `humanize` — `intcomma`, `intword`, `apnumber`, `ordinal`, `naturalday`
535
+ - `cache` — `{% cache timeout key %}...{% endcache %}`
536
+ - `lorem` — `lorem` filter for placeholder text
537
+
538
+ ---
539
+
540
+ ## Custom Filters
541
+
542
+ Register custom filters with `registerFilter`:
543
+
544
+ ```javascript
545
+ const { registerFilter } = require('miki-template');
546
+
547
+ // Simple filter
548
+ registerFilter('reverse', (val) => String(val).split('').reverse().join(''));
549
+
550
+ // Filter with argument
551
+ registerFilter('truncate', (val, length) => {
552
+ const str = String(val);
553
+ if (str.length <= length) return str;
554
+ return str.slice(0, length) + '...';
555
+ });
556
+
557
+ // Chaining works automatically:
558
+ // {{ name|reverse|truncate:5 }}
559
+ ```
560
+
561
+ ---
562
+
563
+ ## Custom Tags
564
+
565
+ Register custom block tags with `registerTag`:
566
+
567
+ ```javascript
568
+ const { registerTag } = require('miki-template');
569
+
570
+ registerTag('markdown', (tagContent, parser) => {
571
+ const body = parser.parse(['endmarkdown']);
572
+ const next = parser.peek();
573
+ if (next && next.type === 'block' && next.content.split(/\s+/)[0] === 'endmarkdown') {
574
+ parser.advance();
575
+ }
576
+ const md = require('markdown-it')();
577
+ return {
578
+ render(context) {
579
+ const html = body.map(n => n.render(context)).join('');
580
+ return md.render(html);
581
+ }
582
+ };
583
+ });
584
+ ```
585
+
586
+ Usage in templates:
587
+ ```html
588
+ {% markdown %}
589
+ # Hello World
590
+ {% endmarkdown %}
591
+ ```
592
+
593
+ ---
594
+
595
+ ## Filter Argument Types
596
+
597
+ Filters accept the following argument types:
598
+
599
+ | Syntax | Type | Example |
600
+ |--------|------|---------|
601
+ | Unquoted | Variable lookup | `{{ value|filter:count }}` |
602
+ | Double-quoted | String literal | `{{ value|filter:"hello" }}` |
603
+ | Single-quoted | String literal | `{{ value|filter:'world' }}` |
604
+ | Number | Integer literal | `{{ value|truncatewords:10 }}` |
605
+
606
+ ```html
607
+ {{ user.name|default:"Guest" }} <!-- String default -->
608
+ {{ items|slice:"1:3" }} <!-- Slice notation -->
609
+ {{ price|floatformat:2 }} <!-- Decimal places -->
610
+ ```