@anydigital/11ty-bricks 1.0.0-alpha.6 → 1.0.0-alpha.8

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 CHANGED
@@ -22,7 +22,7 @@ import eleventyBricks from "@anydigital/11ty-bricks";
22
22
 
23
23
  export default function(eleventyConfig) {
24
24
  eleventyConfig.addPlugin(eleventyBricks, {
25
- autoRaw: true // Enable autoRaw preprocessor (default: false)
25
+ mdAutoRawTags: true // Enable mdAutoRawTags preprocessor (default: false)
26
26
  });
27
27
 
28
28
  // Your other configuration...
@@ -35,7 +35,7 @@ const eleventyBricks = require("@anydigital/11ty-bricks");
35
35
 
36
36
  module.exports = function(eleventyConfig) {
37
37
  eleventyConfig.addPlugin(eleventyBricks, {
38
- autoRaw: true // Enable autoRaw preprocessor (default: false)
38
+ mdAutoRawTags: true // Enable mdAutoRawTags preprocessor (default: false)
39
39
  });
40
40
 
41
41
  // Your other configuration...
@@ -48,11 +48,11 @@ Import only the specific helpers you need without using the plugin:
48
48
 
49
49
  **ES Modules:**
50
50
  ```javascript
51
- import { bricksRegistry, autoRaw } from "@anydigital/11ty-bricks";
51
+ import { bricks, mdAutoRawTags } from "@anydigital/11ty-bricks";
52
52
 
53
53
  export default function(eleventyConfig) {
54
- bricksRegistry(eleventyConfig);
55
- autoRaw(eleventyConfig);
54
+ bricks(eleventyConfig);
55
+ mdAutoRawTags(eleventyConfig);
56
56
 
57
57
  // Your other configuration...
58
58
  }
@@ -60,11 +60,11 @@ export default function(eleventyConfig) {
60
60
 
61
61
  **CommonJS:**
62
62
  ```javascript
63
- const { bricksRegistry, autoRaw } = require("@anydigital/11ty-bricks");
63
+ const { bricks, mdAutoRawTags } = require("@anydigital/11ty-bricks");
64
64
 
65
65
  module.exports = function(eleventyConfig) {
66
- bricksRegistry(eleventyConfig);
67
- autoRaw(eleventyConfig);
66
+ bricks(eleventyConfig);
67
+ mdAutoRawTags(eleventyConfig);
68
68
 
69
69
  // Your other configuration...
70
70
  };
@@ -76,57 +76,64 @@ When using the plugin (Option 1), you can configure which helpers to enable:
76
76
 
77
77
  | Option | Type | Default | Description |
78
78
  |--------|------|---------|-------------|
79
- | `bricksRegistry` | boolean | `false` | Enable the bricksRegistry system for dependency management |
80
- | `autoRaw` | boolean | `false` | Enable the autoRaw preprocessor for Markdown files |
79
+ | `bricks` | boolean | `false` | Enable the bricks system for dependency management |
80
+ | `mdAutoRawTags` | boolean | `false` | Enable the mdAutoRawTags preprocessor for Markdown files |
81
+ | `mdAutoNl2br` | boolean | `false` | Enable the mdAutoNl2br preprocessor to convert \n to `<br>` tags |
82
+ | `fragments` | boolean | `false` | Enable the fragment shortcode for including content from fragments |
83
+ | `setAttrFilter` | boolean | `false` | Enable the setAttr filter for overriding object attributes |
84
+ | `byAttrFilter` | boolean | `false` | Enable the byAttr filter for filtering collections by attribute values |
81
85
 
82
86
  **Example:**
83
87
  ```javascript
84
88
  eleventyConfig.addPlugin(eleventyBricks, {
85
- bricksRegistry: true,
86
- autoRaw: true
89
+ bricks: true,
90
+ mdAutoRawTags: true,
91
+ byAttrFilter: true
87
92
  });
88
93
  ```
89
94
 
90
95
  ## Available 11ty Helpers
91
96
 
92
- ### bricksRegistry
97
+ ### bricks
93
98
 
94
99
  A dependency management system for Eleventy that automatically collects and injects CSS and JavaScript dependencies (both external and inline) per page. This allows brick components to declare their dependencies, and the system will inject them in the correct location in your HTML.
95
100
 
96
101
  **Why use this?**
97
102
 
98
- When building reusable components (bricks) in Eleventy, you often need to include CSS and JavaScript dependencies. Instead of manually adding these to every page, `bricksRegistry` automatically:
103
+ When building reusable components (bricks) in Eleventy, you often need to include CSS and JavaScript dependencies. Instead of manually adding these to every page, `bricks` automatically:
99
104
  - Collects dependencies from all bricks used on a page
100
105
  - Categorizes them (external CSS, external JS, inline styles, inline scripts)
101
106
  - Injects them in the correct location in your HTML output
102
107
 
103
108
  **How it works:**
104
109
 
105
- 1. Use the `bricksRegistry` shortcode in your base template to mark where dependencies should be injected
110
+ 1. Use the `bricksDependencies` shortcode in your base template to mark where dependencies should be injected
106
111
  2. Use the `brick` shortcode to register and render brick components that declare their dependencies
107
112
  3. The system automatically collects all dependencies and injects them when the page is built
108
113
 
109
114
  **Usage:**
110
115
 
111
- 1. Enable `bricksRegistry` in your Eleventy config:
116
+ 1. Enable `bricks` in your Eleventy config:
112
117
 
113
118
  ```javascript
114
- import { bricksRegistry } from "@anydigital/11ty-bricks";
119
+ import { bricks } from "@anydigital/11ty-bricks";
115
120
 
116
121
  export default function(eleventyConfig) {
117
- bricksRegistry(eleventyConfig);
122
+ bricks(eleventyConfig);
118
123
  // Or use as plugin:
119
- // eleventyConfig.addPlugin(eleventyBricks, { bricksRegistry: true });
124
+ // eleventyConfig.addPlugin(eleventyBricks, { bricks: true });
120
125
  }
121
126
  ```
122
127
 
123
- 2. Add the `bricksRegistry` shortcode in your base template (typically in the `<head>` section):
128
+ 2. Add the `bricksDependencies` shortcode in your base template (typically in the `<head>` section):
124
129
 
125
130
  ```njk
126
131
  <head>
127
132
  <meta charset="UTF-8">
128
133
  <title>My Site</title>
129
- {% bricksRegistry %}
134
+ {% bricksDependencies [
135
+ ... (global dependencies can be set here) ...
136
+ ] %}
130
137
  <!-- Other head content -->
131
138
  </head>
132
139
  ```
@@ -192,101 +199,309 @@ The system will automatically inject all dependencies in the order they were reg
192
199
  - Works with both external URLs and inline code
193
200
  - Clears registry before each build to prevent stale data
194
201
 
195
- ### autoRaw
202
+ ### mdAutoRawTags
196
203
 
197
204
  Prevents Nunjucks syntax from being processed in Markdown files by automatically wrapping `{{`, `}}`, `{%`, and `%}` with `{% raw %}` tags.
198
205
 
199
206
  **Why use this?**
200
207
 
201
- When writing documentation or tutorials about templating in Markdown files, you often want to show Nunjucks/Liquid syntax as literal text. This helper automatically escapes these special characters so they display as-is instead of being processed by the template engine.
208
+ When writing documentation or tutorials about templating in Markdown files, you often want to show Nunjucks/Liquid syntax as literal text. This preprocessor automatically escapes these special characters so they display as-is instead of being processed by the template engine.
209
+
210
+ **Usage:**
211
+
212
+ 1. Enable `mdAutoRawTags` in your Eleventy config:
213
+
214
+ ```javascript
215
+ import { mdAutoRawTags } from "@anydigital/11ty-bricks";
216
+
217
+ export default function(eleventyConfig) {
218
+ mdAutoRawTags(eleventyConfig);
219
+ // Or use as plugin:
220
+ // eleventyConfig.addPlugin(eleventyBricks, { mdAutoRawTags: true });
221
+ }
222
+ ```
202
223
 
203
224
  **Example:**
204
225
 
205
- Before `autoRaw`, writing this in Markdown:
226
+ Before `mdAutoRawTags`, writing this in Markdown:
206
227
  ```markdown
207
228
  Use {{ variable }} to output variables.
208
229
  ```
209
230
 
210
- Would try to process `{{ variable }}` as a template variable. With `autoRaw`, it displays exactly as written.
231
+ Would try to process `{{ variable }}` as a template variable. With `mdAutoRawTags`, it displays exactly as written.
211
232
 
212
- ## Available Bricks (Components)
233
+ ### mdAutoNl2br
213
234
 
214
- ### Navigation Macro (`_nav.njk`)
235
+ Automatically converts `\n` sequences to `<br>` tags in Markdown content. This is particularly useful for adding line breaks inside Markdown tables where standard newlines don't work.
215
236
 
216
- A reusable Nunjucks macro for rendering navigation menus with proper accessibility attributes. This macro works seamlessly with the [11ty Navigation Plugin](https://www.11ty.dev/docs/plugins/navigation/).
237
+ **Why use this?**
238
+
239
+ Markdown tables don't support multi-line content in cells. By using `\n` in your content, this preprocessor will convert it to `<br>` tags, allowing you to display line breaks within table cells and other content.
217
240
 
218
241
  **Usage:**
219
242
 
220
- 1. Import the macro in your template:
243
+ 1. Enable `mdAutoNl2br` in your Eleventy config:
221
244
 
222
- ```njk
223
- {% from "bricks/_nav.njk" import render as renderNav %}
245
+ ```javascript
246
+ import { mdAutoNl2br } from "@anydigital/11ty-bricks";
247
+
248
+ export default function(eleventyConfig) {
249
+ mdAutoNl2br(eleventyConfig);
250
+ // Or use as plugin:
251
+ // eleventyConfig.addPlugin(eleventyBricks, { mdAutoNl2br: true });
252
+ }
253
+ ```
254
+
255
+ **Example:**
256
+
257
+ In your Markdown file:
258
+ ```markdown
259
+ | Column 1 | Column 2 |
260
+ |----------|----------|
261
+ | Line 1\nLine 2\nLine 3 | Another cell\nWith multiple lines |
262
+ ```
263
+
264
+ Will render as:
265
+ ```html
266
+ <td>Line 1<br>Line 2<br>Line 3</td>
267
+ <td>Another cell<br>With multiple lines</td>
268
+ ```
269
+
270
+ **Note:** This processes literal `\n` sequences (backslash followed by 'n'), not actual newline characters. Type `\n` in your source files where you want line breaks.
271
+
272
+ ### fragment
273
+
274
+ A shortcode that includes content from fragment files stored in the `_fragments` directory. The content will be processed by the template engine.
275
+
276
+ **Why use this?**
277
+
278
+ Fragments allow you to organize reusable content snippets in a dedicated directory and include them in your templates. This is useful for:
279
+ - Reusable content blocks
280
+ - Shared template sections
281
+ - Component-like content organization
282
+
283
+ **Usage:**
284
+
285
+ 1. Enable `fragment` in your Eleventy config:
286
+
287
+ ```javascript
288
+ import { fragment } from "@anydigital/11ty-bricks";
289
+
290
+ export default function(eleventyConfig) {
291
+ fragment(eleventyConfig);
292
+ // Or use as plugin:
293
+ // eleventyConfig.addPlugin(eleventyBricks, { fragments: true });
294
+ }
224
295
  ```
225
296
 
226
- 2. Call the macro with your navigation data:
297
+ 2. Create fragment files in the `_fragments` directory (relative to your input directory):
298
+
299
+ ```
300
+ your-project/
301
+ _fragments/
302
+ header.njk
303
+ footer.njk
304
+ callout.md
305
+ ```
306
+
307
+ 3. Use the `fragment` shortcode in your templates:
227
308
 
228
309
  ```njk
229
- {{ renderNav(collections.all | eleventyNavigation, page) }}
310
+ {% fragment "header.njk" %}
311
+
312
+ <main>
313
+ <!-- Your content -->
314
+ </main>
315
+
316
+ {% fragment "footer.njk" %}
230
317
  ```
231
318
 
232
319
  **Parameters:**
233
320
 
234
- - `navPages`: Array of navigation entries (typically from `eleventyNavigation` filter)
235
- - `curPage`: Current page object (use Eleventy's `page` variable)
321
+ - `path`: The path to the fragment file relative to the `_fragments` directory
236
322
 
237
323
  **Features:**
238
324
 
239
- - Renders a semantic `<nav>` element
240
- - Automatically adds `aria-current="page"` to the current page link for accessibility
241
- - Clean, minimal markup ready for styling
242
- - Works with nested navigation structures from the 11ty Navigation Plugin
325
+ - Reads files from `_fragments` directory in your input directory
326
+ - Content is processed by the template engine
327
+ - Supports any template language that Eleventy supports
328
+ - Shows helpful error comment if fragment is not found
329
+
330
+ **Example:**
243
331
 
244
- **Example Output:**
332
+ Create `_fragments/callout.njk`:
333
+ ```njk
334
+ <div class="callout callout-{{ type | default('info') }}">
335
+ {{ content }}
336
+ </div>
337
+ ```
245
338
 
246
- ```html
247
- <nav>
248
- <a href="/">Home</a>
249
- <a href="/about/">About</a>
250
- <a href="/contact/" aria-current="page">Contact</a>
251
- </nav>
339
+ Use it in your template:
340
+ ```njk
341
+ {% set type = "warning" %}
342
+ {% set content = "This is important!" %}
343
+ {% fragment "callout.njk" %}
252
344
  ```
253
345
 
254
- ### Google Tag Manager Macro (`_gtm.njk`)
346
+ ### setAttr
347
+
348
+ A filter that creates a new object with an overridden attribute value. This is useful for modifying data objects in templates without mutating the original.
255
349
 
256
- A reusable Nunjucks macro for integrating Google Tag Manager (GTM) into your site. Provides separate macros for the head and body GTM snippets.
350
+ **Why use this?**
351
+
352
+ When working with Eleventy data, you sometimes need to modify an object's properties for a specific use case. The `setAttr` filter provides a clean way to create a modified copy of an object without affecting the original.
257
353
 
258
354
  **Usage:**
259
355
 
260
- 1. Import the macros in your base template:
356
+ 1. Enable `setAttr` in your Eleventy config:
357
+
358
+ ```javascript
359
+ import { setAttr } from "@anydigital/11ty-bricks";
360
+
361
+ export default function(eleventyConfig) {
362
+ setAttr(eleventyConfig);
363
+ // Or use as plugin:
364
+ // eleventyConfig.addPlugin(eleventyBricks, { setAttrFilter: true });
365
+ }
366
+ ```
367
+
368
+ 2. Use the filter in your templates:
261
369
 
262
370
  ```njk
263
- {% import 'bricks/_gtm.njk' as gtm %}
371
+ {# Create a modified version of a page object #}
372
+ {% set modifiedPage = page | setAttr('title', 'New Title') %}
373
+
374
+ <h1>{{ modifiedPage.title }}</h1>
375
+ <p>Original title: {{ page.title }}</p>
264
376
  ```
265
377
 
266
- 2. Call the macros in the appropriate locations:
378
+ **Parameters:**
379
+
380
+ - `obj`: The object to modify
381
+ - `key`: The attribute name to set (string)
382
+ - `value`: The value to set for the attribute (any type)
383
+
384
+ **Returns:**
385
+
386
+ A new object with the specified attribute set to the given value. The original object is not modified.
387
+
388
+ **Features:**
389
+
390
+ - Non-mutating: Creates a new object, leaving the original unchanged
391
+ - Works with any object type
392
+ - Supports any attribute name and value type
393
+ - Can be chained with other filters
394
+
395
+ **Examples:**
267
396
 
268
397
  ```njk
269
- <head>
270
- <!-- Other head content -->
271
- {{ gtm.renderHead('GTM-XXXXXXX') }}
272
- </head>
273
- <body>
274
- {{ gtm.renderBody('GTM-XXXXXXX') }}
275
- <!-- Rest of body content -->
276
- </body>
398
+ {# Override a single attribute #}
399
+ {% set updatedPost = post | setAttr('featured', true) %}
400
+
401
+ {# Chain multiple setAttr filters #}
402
+ {% set modifiedPost = post
403
+ | setAttr('category', 'blog')
404
+ | setAttr('priority', 1)
405
+ %}
406
+
407
+ {# Use in loops #}
408
+ {% for item in collection %}
409
+ {% set enhancedItem = item | setAttr('processed', true) %}
410
+ {# ... use enhancedItem ... #}
411
+ {% endfor %}
412
+ ```
413
+
414
+ ### byAttr
415
+
416
+ A filter that filters collection items by attribute value. It checks if an item's attribute matches a target value. If the attribute is an array, it checks if the array includes the target value.
417
+
418
+ **Why use this?**
419
+
420
+ When working with Eleventy collections, you often need to filter items based on front matter data. The `byAttr` filter provides a flexible way to filter by any attribute, with special handling for array attributes (like tags).
421
+
422
+ **Usage:**
423
+
424
+ 1. Enable `byAttr` in your Eleventy config:
425
+
426
+ ```javascript
427
+ import { byAttr } from "@anydigital/11ty-bricks";
428
+
429
+ export default function(eleventyConfig) {
430
+ byAttr(eleventyConfig);
431
+ // Or use as plugin:
432
+ // eleventyConfig.addPlugin(eleventyBricks, { byAttrFilter: true });
433
+ }
434
+ ```
435
+
436
+ 2. Use the filter in your templates:
437
+
438
+ **Filter by exact attribute match:**
439
+ ```njk
440
+ {# Get all posts with category 'blog' #}
441
+ {% set blogPosts = collections.all | byAttr('category', 'blog') %}
442
+
443
+ {% for post in blogPosts %}
444
+ <h2>{{ post.data.title }}</h2>
445
+ {% endfor %}
446
+ ```
447
+
448
+ **Filter by array attribute (tags):**
449
+ ```njk
450
+ {# Get all posts that include 'javascript' tag #}
451
+ {% set jsPosts = collections.all | byAttr('tags', 'javascript') %}
452
+
453
+ {% for post in jsPosts %}
454
+ <h2>{{ post.data.title }}</h2>
455
+ {% endfor %}
277
456
  ```
278
457
 
279
458
  **Parameters:**
280
459
 
281
- Both macros accept the same parameter:
282
- - `gtmId`: Your Google Tag Manager container ID (e.g., `'GTM-XXXXXXX'`)
460
+ - `collection`: The collection to filter (array of items)
461
+ - `attrName`: The attribute name to check (string)
462
+ - `targetValue`: The value to match against (any type)
283
463
 
284
464
  **Features:**
285
465
 
286
- - Separate macros for head and body placement as recommended by Google
287
- - Clean, standard GTM implementation
288
- - Easy to maintain and update across your site
289
- - Works with all GTM features including noscript fallback
466
+ - Works with any attribute in front matter
467
+ - Handles both `item.data.attrName` and `item.attrName` patterns
468
+ - Special handling for array attributes (uses `includes()` check)
469
+ - Returns empty array if collection is invalid
470
+ - Filters out items without the specified attribute
471
+
472
+ **Examples:**
473
+
474
+ Front matter:
475
+ ```yaml
476
+ ---
477
+ title: My Post
478
+ category: blog
479
+ tags: [javascript, tutorial, beginner]
480
+ priority: 1
481
+ ---
482
+ ```
483
+
484
+ Template usage:
485
+ ```njk
486
+ {# Filter by category #}
487
+ {% set blogPosts = collections.all | byAttr('category', 'blog') %}
488
+
489
+ {# Filter by tag (array) #}
490
+ {% set jsTutorials = collections.all | byAttr('tags', 'javascript') %}
491
+
492
+ {# Filter by numeric value #}
493
+ {% set highPriority = collections.all | byAttr('priority', 1) %}
494
+
495
+ {# Chain filters #}
496
+ {% set recentBlogPosts = collections.all | byAttr('category', 'blog') | reverse | limit(5) %}
497
+ ```
498
+
499
+ ### Additional Exports
500
+
501
+ The plugin also exports the following for advanced usage:
502
+
503
+ - `transformAutoRaw(content)`: The transform function used by `mdAutoRawTags` preprocessor. Can be used programmatically to wrap Nunjucks syntax with raw tags.
504
+ - `transformNl2br(content)`: The transform function used by `mdAutoNl2br` preprocessor. Can be used programmatically to convert `\n` sequences to `<br>` tags.
290
505
 
291
506
  ## CLI Helper Commands
292
507
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anydigital/11ty-bricks",
3
- "version": "1.0.0-alpha.6",
3
+ "version": "1.0.0-alpha.8",
4
4
  "description": "A collection of helpful utilities and filters for Eleventy (11ty)",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -1,4 +1,4 @@
1
- export function bricksRegistry(eleventyConfig) {
1
+ export function bricks(eleventyConfig) {
2
2
 
3
3
  // Brick Registry System
4
4
  // Global registry to track dependencies per page
@@ -23,7 +23,7 @@ export function bricksRegistry(eleventyConfig) {
23
23
  });
24
24
 
25
25
  // brick shortcode: registers and renders a brick component
26
- eleventyConfig.addShortcode("brick", function(brickModule) {
26
+ eleventyConfig.addShortcode("brick", function(brickModule, ...args) {
27
27
  const registry = getPageRegistry(this.page);
28
28
 
29
29
  if (!brickModule) return '';
@@ -47,14 +47,14 @@ export function bricksRegistry(eleventyConfig) {
47
47
 
48
48
  // Render the brick using render() macro
49
49
  if (brickModule.render && typeof brickModule.render === 'function') {
50
- return brickModule.render();
50
+ return brickModule.render(...args);
51
51
  }
52
52
 
53
53
  return '';
54
54
  });
55
55
 
56
56
  // bricksRegistry shortcode: outputs placeholder and base dependencies
57
- eleventyConfig.addShortcode("bricksRegistry", function(dependencies = []) {
57
+ eleventyConfig.addShortcode("bricksDependencies", function(dependencies = []) {
58
58
  const registry = getPageRegistry(this.page);
59
59
 
60
60
  // Register root dependencies if provided (categorized later in transform)
@@ -0,0 +1,35 @@
1
+ /**
2
+ * byAttr filter - Filter collection items by attribute value
3
+ *
4
+ * This filter takes a collection, an attribute name, and a target value,
5
+ * and returns items where the attribute matches the target value.
6
+ * If the attribute is an array, it checks if the array includes the target value.
7
+ *
8
+ * @param {Object} eleventyConfig - The Eleventy configuration object
9
+ */
10
+ export function byAttrFilter(eleventyConfig) {
11
+ eleventyConfig.addFilter("byAttr", function(collection, attrName, targetValue) {
12
+ if (!collection || !Array.isArray(collection)) {
13
+ return [];
14
+ }
15
+
16
+ return collection.filter(item => {
17
+ // Get the attribute value from the item's data
18
+ const attrValue = item?.data?.[attrName] ?? item?.[attrName];
19
+
20
+ // If attribute doesn't exist, skip this item
21
+ if (attrValue === undefined || attrValue === null) {
22
+ return false;
23
+ }
24
+
25
+ // If the attribute is an array, check if it includes the target value
26
+ if (Array.isArray(attrValue)) {
27
+ return attrValue.includes(targetValue);
28
+ }
29
+
30
+ // Otherwise, do a direct comparison
31
+ return attrValue === targetValue;
32
+ });
33
+ });
34
+ }
35
+
@@ -0,0 +1,105 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { byAttrFilter } from './byAttrFilter.js';
4
+
5
+ describe('byAttr filter', () => {
6
+ let filterFn;
7
+
8
+ // Mock eleventyConfig to capture the filter function
9
+ const mockEleventyConfig = {
10
+ addFilter(name, fn) {
11
+ if (name === 'byAttr') {
12
+ filterFn = fn;
13
+ }
14
+ }
15
+ };
16
+
17
+ // Register the filter
18
+ byAttrFilter(mockEleventyConfig);
19
+
20
+ it('should filter items by exact attribute match', () => {
21
+ const collection = [
22
+ { data: { category: 'blog' }, title: 'Post 1' },
23
+ { data: { category: 'news' }, title: 'Post 2' },
24
+ { data: { category: 'blog' }, title: 'Post 3' }
25
+ ];
26
+
27
+ const result = filterFn(collection, 'category', 'blog');
28
+ assert.strictEqual(result.length, 2);
29
+ assert.strictEqual(result[0].title, 'Post 1');
30
+ assert.strictEqual(result[1].title, 'Post 3');
31
+ });
32
+
33
+ it('should filter items when attribute is an array (includes check)', () => {
34
+ const collection = [
35
+ { data: { tags: ['javascript', 'tutorial'] }, title: 'Post 1' },
36
+ { data: { tags: ['python', 'tutorial'] }, title: 'Post 2' },
37
+ { data: { tags: ['javascript', 'advanced'] }, title: 'Post 3' }
38
+ ];
39
+
40
+ const result = filterFn(collection, 'tags', 'javascript');
41
+ assert.strictEqual(result.length, 2);
42
+ assert.strictEqual(result[0].title, 'Post 1');
43
+ assert.strictEqual(result[1].title, 'Post 3');
44
+ });
45
+
46
+ it('should return empty array when collection is not an array', () => {
47
+ const result = filterFn(null, 'category', 'blog');
48
+ assert.strictEqual(result.length, 0);
49
+ });
50
+
51
+ it('should filter out items without the specified attribute', () => {
52
+ const collection = [
53
+ { data: { category: 'blog' }, title: 'Post 1' },
54
+ { data: {}, title: 'Post 2' },
55
+ { data: { category: 'blog' }, title: 'Post 3' }
56
+ ];
57
+
58
+ const result = filterFn(collection, 'category', 'blog');
59
+ assert.strictEqual(result.length, 2);
60
+ });
61
+
62
+ it('should work with attribute directly on item (not in data)', () => {
63
+ const collection = [
64
+ { category: 'blog', title: 'Post 1' },
65
+ { category: 'news', title: 'Post 2' },
66
+ { category: 'blog', title: 'Post 3' }
67
+ ];
68
+
69
+ const result = filterFn(collection, 'category', 'blog');
70
+ assert.strictEqual(result.length, 2);
71
+ });
72
+
73
+ it('should handle mixed data structures', () => {
74
+ const collection = [
75
+ { data: { category: 'blog' }, title: 'Post 1' },
76
+ { category: 'blog', title: 'Post 2' },
77
+ { data: { category: 'news' }, title: 'Post 3' }
78
+ ];
79
+
80
+ const result = filterFn(collection, 'category', 'blog');
81
+ assert.strictEqual(result.length, 2);
82
+ });
83
+
84
+ it('should handle array that does not include target value', () => {
85
+ const collection = [
86
+ { data: { tags: ['python', 'tutorial'] }, title: 'Post 1' },
87
+ { data: { tags: ['ruby', 'guide'] }, title: 'Post 2' }
88
+ ];
89
+
90
+ const result = filterFn(collection, 'tags', 'javascript');
91
+ assert.strictEqual(result.length, 0);
92
+ });
93
+
94
+ it('should handle different value types', () => {
95
+ const collection = [
96
+ { data: { priority: 1 }, title: 'Post 1' },
97
+ { data: { priority: 2 }, title: 'Post 2' },
98
+ { data: { priority: 1 }, title: 'Post 3' }
99
+ ];
100
+
101
+ const result = filterFn(collection, 'priority', 1);
102
+ assert.strictEqual(result.length, 2);
103
+ });
104
+ });
105
+
@@ -0,0 +1,34 @@
1
+ import { readFileSync } from "fs";
2
+ import { join } from "path";
3
+
4
+ /**
5
+ * fragment shortcode - Include content from fragments
6
+ *
7
+ * This shortcode reads a file from the _fragments directory and includes
8
+ * its content. The content will be processed by the template engine.
9
+ *
10
+ * @param {Object} eleventyConfig - The Eleventy configuration object
11
+ */
12
+ export function fragments(eleventyConfig) {
13
+ eleventyConfig.addShortcode("fragment", function(path) {
14
+ // Get the input directory from Eleventy's context
15
+ const inputDir = this.page?.inputPath
16
+ ? join(process.cwd(), eleventyConfig.dir?.input || ".")
17
+ : process.cwd();
18
+
19
+ // Construct the full path to the fragment file
20
+ const fragmentPath = join(inputDir, "_fragments", path);
21
+
22
+ try {
23
+ // Read the fragment file
24
+ const content = readFileSync(fragmentPath, "utf8");
25
+
26
+ // Return content to be processed by the template engine
27
+ return content;
28
+ } catch (error) {
29
+ console.error(`Error reading fragment at ${fragmentPath}:`, error.message);
30
+ return `<!-- Fragment not found: ${path} -->`;
31
+ }
32
+ });
33
+ }
34
+
package/src/index.cjs CHANGED
@@ -9,12 +9,18 @@ module.exports = async function eleventyBricksPlugin(eleventyConfig, options) {
9
9
  return plugin(eleventyConfig, options);
10
10
  };
11
11
 
12
- // Export individual helpers
13
- module.exports.bricksRegistry = async function(eleventyConfig) {
14
- const { bricksRegistry } = await import('./index.js');
15
- return bricksRegistry(eleventyConfig);
16
- };
17
- module.exports.autoRaw = async function(eleventyConfig) {
18
- const { autoRaw } = await import('./index.js');
19
- return autoRaw(eleventyConfig);
20
- };
12
+ // Export individual helpers for granular usage
13
+ ['bricks', 'mdAutoRawTags', 'mdAutoNl2br', 'fragments', 'setAttrFilter', 'byAttrFilter'].forEach(name => {
14
+ module.exports[name] = async (eleventyConfig) => {
15
+ const module = await import('./index.js');
16
+ return module[name](eleventyConfig);
17
+ };
18
+ });
19
+
20
+ // Export transform functions for advanced usage
21
+ ['transformAutoRaw', 'transformNl2br'].forEach(name => {
22
+ module.exports[name] = async (content) => {
23
+ const module = await import('./index.js');
24
+ return module[name](content);
25
+ };
26
+ });
package/src/index.js CHANGED
@@ -1,5 +1,8 @@
1
- import { bricksRegistry } from "./bricksRegistry.js";
2
- import { autoRaw } from "./autoRaw.js";
1
+ import { bricks } from "./bricks.js";
2
+ import { mdAutoRawTags, mdAutoNl2br, transformAutoRaw, transformNl2br } from "./markdown.js";
3
+ import { fragments } from "./fragments.js";
4
+ import { setAttrFilter } from "./setAttrFilter.js";
5
+ import { byAttrFilter } from "./byAttrFilter.js";
3
6
 
4
7
  /**
5
8
  * 11ty Bricks Plugin
@@ -9,18 +12,20 @@ import { autoRaw } from "./autoRaw.js";
9
12
  *
10
13
  * @param {Object} eleventyConfig - The Eleventy configuration object
11
14
  * @param {Object} options - Plugin options
12
- * @param {boolean} options.bricksRegistry - Enable bricksRegistry (default: false)
13
- * @param {boolean} options.autoRaw - Enable autoRaw preprocessor (default: false)
15
+ * @param {boolean} options.bricks - Enable bricks system with dependencies injection (default: false)
16
+ * @param {boolean} options.mdAutoRawTags - Enable mdAutoRawTags preprocessor (default: false)
17
+ * @param {boolean} options.mdAutoNl2br - Enable mdAutoNl2br for \n to <br> conversion (default: false)
18
+ * @param {boolean} options.fragments - Enable fragment shortcode (default: false)
19
+ * @param {boolean} options.setAttrFilter - Enable setAttr filter (default: false)
20
+ * @param {boolean} options.byAttrFilter - Enable byAttr filter (default: false)
14
21
  */
15
22
  export default function eleventyBricksPlugin(eleventyConfig, options = {}) {
16
- if (options.bricksRegistry) {
17
- bricksRegistry(eleventyConfig);
18
- }
19
- if (options.autoRaw) {
20
- autoRaw(eleventyConfig);
21
- }
23
+ const plugins = { bricks, mdAutoRawTags, mdAutoNl2br, fragments, setAttrFilter, byAttrFilter };
24
+ Object.entries(options).forEach(([key, enabled]) => enabled && plugins[key]?.(eleventyConfig));
22
25
  }
23
26
 
24
27
  // Export individual helpers for granular usage
25
- export { bricksRegistry };
26
- export { autoRaw };
28
+ export { bricks, mdAutoRawTags, mdAutoNl2br, fragments, setAttrFilter, byAttrFilter };
29
+
30
+ // Export transform functions for advanced usage
31
+ export { transformAutoRaw, transformNl2br };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Transform Nunjucks syntax in content by wrapping it with raw tags
3
+ *
4
+ * This function wraps Nunjucks syntax ({{, }}, {%, %}) with {% raw %} tags
5
+ * to prevent them from being processed by the template engine.
6
+ *
7
+ * @param {string} content - The content to transform
8
+ * @returns {string} The transformed content with Nunjucks syntax wrapped
9
+ */
10
+ export function transformAutoRaw(content) {
11
+ // This regex looks for {{, }}, {%, or %} individually and wraps them
12
+ return content.replace(/({{|}}|{%|%})/g, "{% raw %}$1{% endraw %}");
13
+ }
14
+
15
+ /**
16
+ * mdAutoRawTags - Forbid Nunjucks processing in Markdown files
17
+ *
18
+ * This preprocessor wraps Nunjucks syntax ({{, }}, {%, %}) with {% raw %} tags
19
+ * to prevent them from being processed by the template engine in Markdown files.
20
+ *
21
+ * @param {Object} eleventyConfig - The Eleventy configuration object
22
+ */
23
+ export function mdAutoRawTags(eleventyConfig) {
24
+ eleventyConfig.addPreprocessor("mdAutoRawTags", "md", (data, content) => {
25
+ return transformAutoRaw(content);
26
+ });
27
+ }
28
+
29
+ /**
30
+ * Transform \n sequences to <br> tags
31
+ *
32
+ * This function converts literal \n sequences (double backslash + n) to HTML <br> tags.
33
+ * It handles both double \n\n and single \n sequences, processing double ones first.
34
+ *
35
+ * @param {string} content - The content to transform
36
+ * @returns {string} The transformed content with \n converted to <br>
37
+ */
38
+ export function transformNl2br(content) {
39
+ // Replace double \n\n first, then single \n to avoid double conversion
40
+ return content.replace(/\\n\\n/g, '<br>').replace(/\\n/g, '<br>');
41
+ }
42
+
43
+ /**
44
+ * mdAutoNl2br - Auto convert \n to <br> in markdown (especially tables)
45
+ *
46
+ * This function amends the markdown library to automatically convert \n
47
+ * to <br> tags in text content, which is particularly useful for line breaks
48
+ * inside markdown tables where standard newlines don't work.
49
+ *
50
+ * @param {Object} eleventyConfig - The Eleventy configuration object
51
+ */
52
+ export function mdAutoNl2br(eleventyConfig) {
53
+ eleventyConfig.amendLibrary("md", mdLib => {
54
+ mdLib.renderer.rules.text = (tokens, idx) => {
55
+ return transformNl2br(tokens[idx].content);
56
+ };
57
+ });
58
+ }
@@ -1,6 +1,6 @@
1
1
  import { describe, it } from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { transformAutoRaw } from "./autoRaw.js";
3
+ import { transformAutoRaw, transformNl2br } from "./markdown.js";
4
4
 
5
5
  describe("transformAutoRaw", () => {
6
6
  it("should wrap opening double curly braces with raw tags", () => {
@@ -85,3 +85,68 @@ Some text
85
85
  });
86
86
  });
87
87
 
88
+ describe("transformNl2br", () => {
89
+ it("should convert single \\n to <br>", () => {
90
+ const input = "Line 1\\nLine 2";
91
+ const expected = "Line 1<br>Line 2";
92
+ assert.equal(transformNl2br(input), expected);
93
+ });
94
+
95
+ it("should convert double \\n\\n to <br>", () => {
96
+ const input = "Line 1\\n\\nLine 2";
97
+ const expected = "Line 1<br>Line 2";
98
+ assert.equal(transformNl2br(input), expected);
99
+ });
100
+
101
+ it("should convert multiple \\n sequences", () => {
102
+ const input = "Line 1\\nLine 2\\nLine 3";
103
+ const expected = "Line 1<br>Line 2<br>Line 3";
104
+ assert.equal(transformNl2br(input), expected);
105
+ });
106
+
107
+ it("should handle mixed single and double \\n", () => {
108
+ const input = "Line 1\\n\\nLine 2\\nLine 3";
109
+ const expected = "Line 1<br>Line 2<br>Line 3";
110
+ assert.equal(transformNl2br(input), expected);
111
+ });
112
+
113
+ it("should handle text without \\n", () => {
114
+ const input = "Just plain text";
115
+ assert.equal(transformNl2br(input), input);
116
+ });
117
+
118
+ it("should handle empty content", () => {
119
+ assert.equal(transformNl2br(""), "");
120
+ });
121
+
122
+ it("should handle content with only \\n", () => {
123
+ const input = "\\n\\n\\n";
124
+ const expected = "<br><br>";
125
+ assert.equal(transformNl2br(input), expected);
126
+ });
127
+
128
+ it("should handle markdown table cell content with \\n", () => {
129
+ const input = "Cell 1\\nCell 1 Line 2\\n\\nCell 1 Line 3";
130
+ const expected = "Cell 1<br>Cell 1 Line 2<br>Cell 1 Line 3";
131
+ assert.equal(transformNl2br(input), expected);
132
+ });
133
+
134
+ it("should handle multiple consecutive double \\n\\n", () => {
135
+ const input = "Line 1\\n\\n\\n\\nLine 2";
136
+ const expected = "Line 1<br><br>Line 2";
137
+ assert.equal(transformNl2br(input), expected);
138
+ });
139
+
140
+ it("should preserve actual newlines (not literal \\n)", () => {
141
+ const input = "Line 1\nLine 2";
142
+ const expected = "Line 1\nLine 2";
143
+ assert.equal(transformNl2br(input), expected);
144
+ });
145
+
146
+ it("should only convert literal backslash-n sequences", () => {
147
+ const input = "Text with\\nbackslash-n and\nreal newline";
148
+ const expected = "Text with<br>backslash-n and\nreal newline";
149
+ assert.equal(transformNl2br(input), expected);
150
+ });
151
+ });
152
+
@@ -0,0 +1,17 @@
1
+ /**
2
+ * setAttr filter - Override an attribute and return the object
3
+ *
4
+ * This filter takes an object, a key, and a value, and returns a new object
5
+ * with the specified attribute set to the given value.
6
+ *
7
+ * @param {Object} eleventyConfig - The Eleventy configuration object
8
+ */
9
+ export function setAttrFilter(eleventyConfig) {
10
+ eleventyConfig.addFilter("setAttr", function(obj, key, value) {
11
+ return {
12
+ ...obj,
13
+ [key]: value
14
+ };
15
+ });
16
+ }
17
+
package/src/autoRaw.js DELETED
@@ -1,28 +0,0 @@
1
- /**
2
- * Transform Nunjucks syntax in content by wrapping it with raw tags
3
- *
4
- * This function wraps Nunjucks syntax ({{, }}, {%, %}) with {% raw %} tags
5
- * to prevent them from being processed by the template engine.
6
- *
7
- * @param {string} content - The content to transform
8
- * @returns {string} The transformed content with Nunjucks syntax wrapped
9
- */
10
- export function transformAutoRaw(content) {
11
- // This regex looks for {{, }}, {%, or %} individually and wraps them
12
- return content.replace(/({{|}}|{%|%})/g, "{% raw %}$1{% endraw %}");
13
- }
14
-
15
- /**
16
- * autoRaw - Forbid Nunjucks processing in Markdown files
17
- *
18
- * This preprocessor wraps Nunjucks syntax ({{, }}, {%, %}) with {% raw %} tags
19
- * to prevent them from being processed by the template engine in Markdown files.
20
- *
21
- * @param {Object} eleventyConfig - The Eleventy configuration object
22
- */
23
- export function autoRaw(eleventyConfig) {
24
- eleventyConfig.addPreprocessor("autoRaw", "md", (data, content) => {
25
- return transformAutoRaw(content);
26
- });
27
- }
28
-
@@ -1,16 +0,0 @@
1
- {% macro renderHead(gtmId) %}
2
- <!-- Google Tag Manager -->
3
- <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
4
- new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
5
- j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
6
- 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
7
- })(window,document,'script','dataLayer','{{ gtmId }}');</script>
8
- <!-- End Google Tag Manager -->
9
- {% endmacro %}
10
-
11
- {% macro renderBody(gtmId) %}
12
- <!-- Google Tag Manager (noscript) -->
13
- <noscript><iframe src="https://www.googletagmanager.com/ns.html?id={{ gtmId }}"
14
- height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
15
- <!-- End Google Tag Manager (noscript) -->
16
- {% endmacro %}
@@ -1,10 +0,0 @@
1
- {% macro render(navPages, curPage) %}
2
- {# https://www.11ty.dev/docs/plugins/navigation/#bring-your-own-html-render-the-menu-items-manually #}
3
- <nav>
4
- {%- for entry in navPages %}
5
- <a href="{{ entry.url }}" {{ 'aria-current="page"' | safe if entry.url == curPage.url }}>
6
- {{- entry.title -}}
7
- </a>
8
- {%- endfor %}
9
- </nav>
10
- {% endmacro %}