@anydigital/eleventy-bricks 0.26.0 → 0.27.1

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
@@ -27,7 +27,7 @@ export default function (eleventyConfig) {
27
27
  mdAutoNl2br: true,
28
28
  autoLinkFavicons: true,
29
29
  siteData: true,
30
- filters: ["attr_set", "attr_includes", "merge", "remove_tag", "if", "attr_concat", "fetch"],
30
+ filters: ["attr_set", "attr_includes", "merge", "remove_tag", "if", "attr_concat", "section", "fetch"],
31
31
  });
32
32
 
33
33
  // Your other configuration...
@@ -45,7 +45,7 @@ module.exports = function (eleventyConfig) {
45
45
  mdAutoNl2br: true,
46
46
  autoLinkFavicons: true,
47
47
  siteData: true,
48
- filters: ["attr_set", "attr_includes", "merge", "remove_tag", "if", "attr_concat", "fetch"],
48
+ filters: ["attr_set", "attr_includes", "merge", "remove_tag", "if", "attr_concat", "section", "fetch"],
49
49
  });
50
50
 
51
51
  // Your other configuration...
@@ -71,6 +71,7 @@ import {
71
71
  removeTagFilter,
72
72
  ifFilter,
73
73
  attrConcatFilter,
74
+ sectionFilter,
74
75
  fetchFilter,
75
76
  siteData,
76
77
  } from "@anydigital/eleventy-bricks";
@@ -85,6 +86,7 @@ export default function (eleventyConfig) {
85
86
  removeTagFilter(eleventyConfig);
86
87
  ifFilter(eleventyConfig);
87
88
  attrConcatFilter(eleventyConfig);
89
+ sectionFilter(eleventyConfig);
88
90
  // fetchFilter is only available if @11ty/eleventy-fetch is installed
89
91
  if (fetchFilter) {
90
92
  fetchFilter(eleventyConfig);
@@ -108,6 +110,7 @@ const {
108
110
  removeTagFilter,
109
111
  ifFilter,
110
112
  attrConcatFilter,
113
+ sectionFilter,
111
114
  fetchFilter,
112
115
  siteData,
113
116
  } = require("@anydigital/eleventy-bricks");
@@ -122,6 +125,7 @@ module.exports = async function (eleventyConfig) {
122
125
  await removeTagFilter(eleventyConfig);
123
126
  await ifFilter(eleventyConfig);
124
127
  await attrConcatFilter(eleventyConfig);
128
+ await sectionFilter(eleventyConfig);
125
129
  // fetchFilter is only available if @11ty/eleventy-fetch is installed
126
130
  if (fetchFilter) {
127
131
  await fetchFilter(eleventyConfig);
@@ -154,6 +158,7 @@ When using the plugin (Option 1), you can configure which helpers to enable:
154
158
  - `'remove_tag'` - Remove HTML elements from content
155
159
  - `'if'` - Inline conditional/ternary operator
156
160
  - `'attr_concat'` - Concatenate values to an attribute array
161
+ - `'section'` - Extract named sections from content marked with HTML comments
157
162
  - `'fetch'` - Fetch remote URLs or local files (requires `@11ty/eleventy-fetch`)
158
163
 
159
164
  **Example:**
@@ -164,7 +169,7 @@ eleventyConfig.addPlugin(eleventyBricks, {
164
169
  mdAutoNl2br: true,
165
170
  autoLinkFavicons: true,
166
171
  siteData: true,
167
- filters: ["attr_set", "attr_includes", "merge", "remove_tag", "if", "attr_concat", "fetch"],
172
+ filters: ["attr_set", "attr_includes", "merge", "remove_tag", "if", "attr_concat", "section", "fetch"],
168
173
  });
169
174
  ```
170
175
 
@@ -172,50 +177,40 @@ eleventyConfig.addPlugin(eleventyBricks, {
172
177
 
173
178
  The plugin also exports the following utility functions for advanced usage:
174
179
 
175
- - `transformAutoRaw(content)`: The transform function used by `mdAutoRawTags` preprocessor. Can be used programmatically to wrap Nunjucks syntax with raw tags.
176
- - `transformNl2br(content)`: The transform function used by `mdAutoNl2br` preprocessor. Can be used programmatically to convert `\n` sequences to `<br>` tags.
180
+ - `transformAutoRaw(content)`: The processor function used by `mdAutoRawTags` preprocessor. Can be used programmatically to wrap Nunjucks syntax with raw tags.
181
+ - `transformNl2br(content)`: The processor function used by `mdAutoNl2br` preprocessor. Can be used programmatically to convert `\\n` sequences to `\u003cbr\u003e` tags.
177
182
  - `isPlainUrlText(linkText, domain)`: Helper function that checks if link text looks like a plain URL or domain.
178
183
  - `cleanLinkText(linkText, domain)`: Helper function that cleans link text by removing protocol, domain, and leading slash.
179
184
  - `buildFaviconLink(attrs, domain, text)`: Helper function that builds HTML for a link with favicon.
180
- - `transformLink(match, attrs, url, linkText)`: The transform function used by `autoLinkFavicons` that transforms a single link to include a favicon.
181
- - `replaceLinksInHtml(content, transformer)`: Helper function that replaces all anchor links in HTML content with transformed versions.
185
+ - `transformLink(match, attrs, url, linkText)`: The processor function used by `autoLinkFavicons` that processes a single link to include a favicon.
186
+ - `replaceLinksInHtml(content, processor)`: Helper function that replaces all anchor links in HTML content with processed versions.
182
187
  - `attrIncludes(collection, attrName, targetValue)`: The core logic for filtering collection items by checking if an attribute array includes a target value. Can be used programmatically to filter collections.
183
188
  - `merge(first, ...rest)`: The core merge function used by the `merge` filter. Can be used programmatically to merge arrays or objects.
184
189
  - `removeTag(html, tagName)`: The core function used by the `remove_tag` filter. Can be used programmatically to remove HTML tags from content.
185
190
  - `iff(trueValue, condition, falseValue)`: The core conditional function used by the `if` filter. Can be used programmatically as a ternary operator.
186
191
  - `attrConcat(obj, attr, values)`: The core function used by the `attr_concat` filter. Can be used programmatically to concatenate values to an attribute array.
187
192
  - `attrSet(obj, key, value)`: The core function used by the `attr_set` filter. Can be used programmatically to override object attributes.
193
+ - `section(content, sectionName)`: The core function used by the `section` filter. Can be used programmatically to extract named sections from content.
188
194
 
189
- <!--TRICKS-->
190
-
191
- ## Tricks from [Eleventy Bricks](https://github.com/anydigital/eleventy-bricks) {#eleventy-bricks}
192
-
193
- ### Filters
194
-
195
- | Input | Nunjucks | Liquid <hr> |
196
- | ---------: | ------------------------------------------------------------------------------ | ---------------------------------------------------- |
197
- | {.divider} | Logical |
198
- | `ANY \| ` | `default(VALUE)` <br>= `d(...)` | `default: VALUE` |
199
- | `ANY \|` | [`if(TEST, OP, VALUE)`](#if) <sub>currently only `if(TEST)`</sub> | [`if: TEST, OP, VALUE`](#if) |
200
- | {.divider} | On objects |
201
- | `OBJ \|` | [`merge(OBJ2)`](#merge) | [`merge: OBJ2`](#merge) |
202
- | {.divider} | On object attributes |
203
- | `OBJ \|` | `selectattr(BOOL_ATTR)` | `where: ATTR, VALUE` |
204
- | `OBJ \|` | `rejectattr(BOOL_ATTR)` | N/A |
205
- | `OBJ \|` | [`attr_includes(ARRAY_ATTR, VALUE)`](#attr_includes) <sub>was `where_in`</sub> | [`attr_includes: ARRAY_ATTR, VALUE`](#attr_includes) |
206
- | `OBJ \|` | [`attr_set(ATTR, VALUE)`](#attr_set) <sub>was `attr`</sub> | [`attr_set: ATTR, VALUE`](#attr_set) |
207
- | `OBJ \|` | [`attr_concat(ARRAY_ATTR, ARRAY2)`](#attr_concat) | [`attr_concat: ARRAY_ATTR, ARRAY2`](#attr_concat) |
208
- | {.divider} | Textual |
209
- | `HTML \|` | `striptags` | `strip_html` |
210
- | `HTML \|` | [`remove_tag(TAG)`](#remove_tag) | [`remove_tag: TAG`](#remove_tag) |
211
- | `STR \|` | `remove: STR2` | `remove: STR2` |
212
- | {.divider} | Other |
213
- | `URL \|` | [`fetch`](#fetch) | [`fetch`](#fetch) |
214
-
215
- Ref:
216
-
217
- - https://mozilla.github.io/nunjucks/templating.html#builtin-filters
218
- - https://shopify.github.io/liquid/
195
+ ## Features
196
+
197
+ <!--section:filters-h3-->
198
+
199
+ ### Universal 11ty filters <small>for `.njk` & `.liquid`</small> <sub>by https://github.com/anydigital/eleventy-bricks</sub>
200
+
201
+ | Input | Filter | Arguments |
202
+ | ---------: | --------------------------------- | -------------------------------------------------- |
203
+ | {.divider} | Logical filters: |
204
+ | `ANY \|` | [`if`](#if) | `TEST, OP, VALUE` <sub>currently only `TEST`</sub> |
205
+ | {.divider} | Filters for objects: |
206
+ | `OBJ \|` | [`merge`](#merge) | `OBJ2` |
207
+ | `OBJ \|` | [`attr_set`](#attr_set) | `ATTR, VALUE` |
208
+ | `OBJ \|` | [`attr_concat`](#attr_concat) | `ATTR, ARRAY2` |
209
+ | `OBJ \|` | [`attr_includes`](#attr_includes) | `ATTR, VALUE` |
210
+ | {.divider} | Other filters: |
211
+ | `URL \|` | [`fetch`](#fetch) | |
212
+ | `HTML \|` | [`section`](#section) | `NAME` |
213
+ | `HTML \|` | [`remove_tag`](#remove_tag) | `TAG` |
219
214
 
220
215
  #### `attr_set`
221
216
 
@@ -520,6 +515,103 @@ export default function (eleventyConfig) {
520
515
 
521
516
  While this filter can help sanitize HTML content, it should not be relied upon as the sole security measure. For critical security requirements, use a dedicated HTML sanitization library on the server side before content reaches your templates.
522
517
 
518
+ #### `section`
519
+
520
+ A filter that extracts a named section from content marked with HTML comments. This is useful for splitting a single content file (like a Markdown post) into multiple parts that can be displayed and styled independently in your templates.
521
+
522
+ **Why use this?**
523
+
524
+ When working with Markdown content in Eleventy, you're usually limited to a single `content` variable. The `section` filter allows you to define multiple named sections within your content using simple HTML comments, giving you granular control over where different parts of your content appear in your layout.
525
+
526
+ **Usage:**
527
+
528
+ 1. Enable the `section` filter in your Eleventy config:
529
+
530
+ ```javascript
531
+ import { sectionFilter } from "@anydigital/eleventy-bricks";
532
+
533
+ export default function (eleventyConfig) {
534
+ sectionFilter(eleventyConfig);
535
+ // Or use as plugin:
536
+ // eleventyConfig.addPlugin(eleventyBricks, { filters: ['section'] });
537
+ }
538
+ ```
539
+
540
+ 2. Mark sections in your content file (e.g., `post.md`):
541
+
542
+ ```markdown
543
+ # My Post
544
+
545
+ <¡--section:intro-->
546
+
547
+ This is the introduction that appears at the top of the page.
548
+
549
+ <¡--section:main-->
550
+
551
+ This is the main body of the post with all the details.
552
+
553
+ <¡--section:summary,sidebar-->
554
+
555
+ This content appears in both the summary and the sidebar!
556
+ ```
557
+
558
+ 3. Use the filter in your templates:
559
+
560
+ ```njk
561
+ {# Get the intro section #}
562
+ <div class="page-intro">
563
+ {{ content | section('intro') | safe }}
564
+ </div>
565
+
566
+ {# Get the main section #}
567
+ <article>
568
+ {{ content | section('main') | safe }}
569
+ </article>
570
+
571
+ {# Get the sidebar section #}
572
+ <aside>
573
+ {{ content | section('sidebar') | safe }}
574
+ </aside>
575
+ ```
576
+
577
+ **Parameters:**
578
+
579
+ - `content`: The string content to process (usually `content` variable)
580
+ - `sectionName`: The name(s) of the section to extract (string)
581
+
582
+ **Features:**
583
+
584
+ - **Multiple names**: A single section can have multiple names separated by commas: `<¡--section:name1,name2-->`
585
+ - **Case-insensitive**: Section names are matched without regard to case
586
+ - **Multiple occurrences**: If a section name appears multiple times, the filter concatenates all matching sections
587
+ - **Non-destructive**: Returns extracted content without modifying the original input
588
+ - **EOF support**: Sections continue until the next `<¡--section*-->` marker or the end of the file
589
+
590
+ **Examples:**
591
+
592
+ ```njk
593
+ {# Extract multiple sections with same name #}
594
+ {# Example content has two &lt;!--section:note--> blocks #}
595
+ <div class="notes-box">
596
+ {{ content | section('note') | safe }}
597
+ </div>
598
+
599
+ {# Use case-insensitive names #}
600
+ {{ content | section('INTRO') | safe }}
601
+
602
+ {# Handle missing sections gracefully (returns empty string) #}
603
+ {% set footer = content | section('non-existent-section') %}
604
+ {% if footer %}
605
+ <footer>{{ footer | safe }}</footer>
606
+ {% endif %}
607
+ ```
608
+
609
+ **Syntax Rules:**
610
+
611
+ - Sections start with: `<¡--section:NAME-->` or `<¡--section:NAME1,NAME2-->`
612
+ - Sections end at the next `<¡--section*-->` marker or end of file
613
+ - Whitespace around names and inside comments is automatically trimmed
614
+
523
615
  #### `if`
524
616
 
525
617
  An inline conditional/ternary operator filter that returns one value if a condition is truthy, and another if it's falsy. Similar to Nunjucks' inline if syntax.
@@ -810,61 +902,108 @@ your-project/
810
902
 
811
903
  **Note:** The filter returns raw text content. Use Eleventy's built-in filters like `| safe`, `| markdown`, or `| fromJson` to process the content as needed.
812
904
 
813
- ### Transforms
905
+ <!--section:data&processors-h3-->
814
906
 
815
- #### mdAutoRawTags
907
+ ### Global `site` data helpers
816
908
 
817
- Prevents Nunjucks syntax from being processed in Markdown files by automatically wrapping `{{`, `}}`, `{%`, and `%}` with `{% raw %}` tags.
909
+ Adds global `site` data to your Eleventy project, providing commonly needed values that can be accessed in all templates:
818
910
 
819
- **Why use this?**
911
+ | Variable | Value |
912
+ | ----------------- | ------------------------------------------------------------------------------------------------------------ |
913
+ | `{{ site.year }}` | The current year as a number (e.g., `2026`) |
914
+ | `{{ site.prod }}` | Boolean indicating if running in production mode (`true` for `eleventy build`, `false` for `eleventy serve`) |
820
915
 
821
- 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.
916
+ <details>
917
+ <summary>Quick setup</summary>
822
918
 
823
- **Usage:**
919
+ ```sh
920
+ npm install @anydigital/eleventy-bricks
921
+ ```
824
922
 
825
- 1. Enable `mdAutoRawTags` in your Eleventy config:
923
+ Then choose one of the following options:
826
924
 
827
- ```javascript
828
- import { mdAutoRawTags } from "@anydigital/eleventy-bricks";
925
+ 1. ```js {data-caption="As a plugin in eleventy.config.js (balanced)"}
926
+ import eleventyBricksPlugin from "@anydigital/eleventy-bricks";
829
927
 
830
- export default function (eleventyConfig) {
831
- mdAutoRawTags(eleventyConfig);
832
- // Or use as plugin:
833
- // eleventyConfig.addPlugin(eleventyBricks, { mdAutoRawTags: true });
834
- }
835
- ```
928
+ export default function (eleventyConfig) {
929
+ eleventyConfig.addPlugin(eleventyBricksPlugin, { siteData: true });
930
+ }
931
+ ```
932
+
933
+ 2. ```js {data-caption="Individual import in eleventy.config.js (minimal)"}
934
+ import { siteData } from "@anydigital/eleventy-bricks";
935
+
936
+ export default function (eleventyConfig) {
937
+ siteData(eleventyConfig);
938
+ }
939
+ ```
940
+
941
+ 3. ```sh {data-caption="Symlink entire eleventy.config.js (easiest)"}
942
+ ln -s node_modules/@anydigital/eleventy-bricks/src/eleventy.config.js
943
+ ```
944
+
945
+ {.list-[upper-roman]}
946
+
947
+ </details>
948
+
949
+ ### `mdAutoRawTags` preprocessor
950
+
951
+ Prevents Nunjucks syntax from being processed in Markdown files by automatically wrapping `{{`, `}}`, `{%`, and `%}` with `{% raw %}` tags.
952
+
953
+ **Why use this?**
954
+
955
+ 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.
836
956
 
837
957
  **Example:**
838
958
 
839
959
  Before `mdAutoRawTags`, writing this in Markdown:
840
960
 
841
961
  ```markdown
842
- Use {{ variable }} to output variables.
962
+ ### Using {{ variable }} to output variables
843
963
  ```
844
964
 
845
965
  Would try to process `{{ variable }}` as a template variable. With `mdAutoRawTags`, it displays exactly as written.
846
966
 
847
- #### mdAutoNl2br
967
+ <details>
968
+ <summary>Quick setup</summary>
848
969
 
849
- 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.
970
+ ```sh
971
+ npm install @anydigital/eleventy-bricks
972
+ ```
850
973
 
851
- **Why use this?**
974
+ Then choose one of the following options:
852
975
 
853
- 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.
976
+ 1. ```js {data-caption="As a plugin in eleventy.config.js (balanced)"}
977
+ import eleventyBricksPlugin from "@anydigital/eleventy-bricks";
854
978
 
855
- **Usage:**
979
+ export default function (eleventyConfig) {
980
+ eleventyConfig.addPlugin(eleventyBricksPlugin, { mdAutoRawTags: true });
981
+ }
982
+ ```
856
983
 
857
- 1. Enable `mdAutoNl2br` in your Eleventy config:
984
+ 2. ```js {data-caption="Individual import in eleventy.config.js (minimal)"}
985
+ import { mdAutoRawTags } from "@anydigital/eleventy-bricks";
858
986
 
859
- ```javascript
860
- import { mdAutoNl2br } from "@anydigital/eleventy-bricks";
987
+ export default function (eleventyConfig) {
988
+ mdAutoRawTags(eleventyConfig);
989
+ }
990
+ ```
861
991
 
862
- export default function (eleventyConfig) {
863
- mdAutoNl2br(eleventyConfig);
864
- // Or use as plugin:
865
- // eleventyConfig.addPlugin(eleventyBricks, { mdAutoNl2br: true });
866
- }
867
- ```
992
+ 3. ```sh {data-caption="Symlink entire eleventy.config.js (easiest)"}
993
+ ln -s node_modules/@anydigital/eleventy-bricks/src/eleventy.config.js
994
+ ```
995
+
996
+ {.list-[upper-roman]}
997
+
998
+ </details>
999
+
1000
+ ### `mdAutoNl2br` converter
1001
+
1002
+ 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.
1003
+
1004
+ **Why use this?**
1005
+
1006
+ 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.
868
1007
 
869
1008
  **Example:**
870
1009
 
@@ -885,31 +1024,48 @@ Will render as:
885
1024
 
886
1025
  **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.
887
1026
 
888
- #### autoLinkFavicons
1027
+ <details>
1028
+ <summary>Quick setup</summary>
889
1029
 
890
- Automatically adds favicon images from Google's favicon service to links that display plain URLs or domain names. This transform processes all HTML output files and adds inline favicon images next to link text that appears to be a plain URL.
1030
+ ```sh
1031
+ npm install @anydigital/eleventy-bricks
1032
+ ```
891
1033
 
892
- **Why use this?**
1034
+ Then choose one of the following options:
893
1035
 
894
- When you have links in your content that display raw URLs or domain names (like `https://example.com/page`), adding favicons provides a visual indicator of the external site. This transform automatically detects these plain-text URL links and enhances them with favicon images, making them more visually appealing and easier to recognize.
1036
+ 1. ```js {data-caption="As a plugin in eleventy.config.js (balanced)"}
1037
+ import eleventyBricksPlugin from "@anydigital/eleventy-bricks";
895
1038
 
896
- **Usage:**
1039
+ export default function (eleventyConfig) {
1040
+ eleventyConfig.addPlugin(eleventyBricksPlugin, { mdAutoNl2br: true });
1041
+ }
1042
+ ```
897
1043
 
898
- 1. Enable `autoLinkFavicons` in your Eleventy config:
1044
+ 2. ```js {data-caption="Individual import in eleventy.config.js (minimal)"}
1045
+ import { mdAutoNl2br } from "@anydigital/eleventy-bricks";
899
1046
 
900
- ```javascript
901
- import { autoLinkFavicons } from "@anydigital/eleventy-bricks";
1047
+ export default function (eleventyConfig) {
1048
+ mdAutoNl2br(eleventyConfig);
1049
+ }
1050
+ ```
902
1051
 
903
- export default function (eleventyConfig) {
904
- autoLinkFavicons(eleventyConfig);
905
- // Or use as plugin:
906
- // eleventyConfig.addPlugin(eleventyBricks, { autoLinkFavicons: true });
907
- }
908
- ```
1052
+ 3. ```sh {data-caption="Symlink entire eleventy.config.js (easiest)"}
1053
+ ln -s node_modules/@anydigital/eleventy-bricks/src/eleventy.config.js
1054
+ ```
909
1055
 
910
- **How it works:**
1056
+ {.list-[upper-roman]}
1057
+
1058
+ </details>
1059
+
1060
+ ### `autoLinkFavicons` processor
1061
+
1062
+ Automatically adds favicon images from Google's favicon service to links that display plain URLs or domain names. This processor processes all HTML output files and adds inline favicon images next to link text that appears to be a plain URL.
1063
+
1064
+ **Why use this?**
1065
+
1066
+ When you have links in your content that display raw URLs or domain names (like `https://example.com/page`), adding favicons provides a visual indicator of the external site. This processor automatically detects these plain-text URL links and enhances them with favicon images, making them more visually appealing and easier to recognize.
911
1067
 
912
- The transform:
1068
+ **How it works:**
913
1069
 
914
1070
  1. Scans all HTML output files for `<a>` tags
915
1071
  2. Checks if the link text appears to be a plain URL or domain
@@ -919,13 +1075,13 @@ The transform:
919
1075
 
920
1076
  **Example:**
921
1077
 
922
- Before transformation:
1078
+ Before processing:
923
1079
 
924
1080
  ```html
925
1081
  <a href="https://github.com/anydigital/eleventy-bricks">https://github.com/anydigital/eleventy-bricks</a>
926
1082
  ```
927
1083
 
928
- After transformation:
1084
+ After processing:
929
1085
 
930
1086
  ```html
931
1087
  <a href="https://github.com/anydigital/eleventy-bricks" class="whitespace-nowrap" target="_blank">
@@ -941,124 +1097,49 @@ After transformation:
941
1097
  - Removes the trailing slash from the display text
942
1098
  - Only applies if at least 3 characters remain after removing the domain (to avoid showing favicons for bare domain links)
943
1099
  - Uses Google's favicon service at `https://www.google.com/s2/favicons?domain=DOMAIN&sz=32`
944
- - Adds `target="_blank"` to the transformed links (only if not already present)
1100
+ - Adds `target="_blank"` to the processed links (only if not already present)
945
1101
  - Adds `whitespace-nowrap` class to the link
946
1102
  - Wraps the link text in a `<span>` element
947
1103
  - The favicon is wrapped in an `<i>` tag for easy styling
948
1104
 
949
- **Styling:**
950
-
951
- You can style the favicon icons with CSS:
952
-
953
- ```css
954
- /* Style the favicon wrapper */
955
- a i {
956
- display: inline-block;
957
- margin-right: 0.25em;
958
- }
959
-
960
- /* Style the favicon image */
961
- a i img {
962
- width: 16px;
963
- height: 16px;
964
- vertical-align: middle;
965
- }
966
- ```
967
-
968
- **Note:** This transform only processes HTML output files (those ending in `.html`). It does not modify the original content files.
969
-
970
- ### Global Data
971
-
972
- Adds global site data to your Eleventy project, providing commonly needed values that can be accessed in all templates.
973
-
974
- **Why use this?**
975
-
976
- Many websites need access to the current year (for copyright notices) and environment information (to conditionally enable features based on production vs development). This helper provides these as global `site` data without manually setting them up.
977
-
978
- **Usage:**
979
-
980
- 1. Enable `siteData` in your Eleventy config:
1105
+ <details>
1106
+ <summary>Quick setup</summary>
981
1107
 
982
- ```javascript
983
- import { siteData } from "@anydigital/eleventy-bricks";
984
-
985
- export default function (eleventyConfig) {
986
- siteData(eleventyConfig);
987
- // Or use as plugin:
988
- // eleventyConfig.addPlugin(eleventyBricks, { siteData: true });
989
- }
990
- ```
991
-
992
- 2. Use the global data in your templates:
993
-
994
- **Current Year:**
995
-
996
- ```njk
997
- <footer>
998
- <p>&copy; {{ site.year }} Your Company Name. All rights reserved.</p>
999
- </footer>
1000
- ```
1001
-
1002
- **Environment Check:**
1003
-
1004
- ```njk
1005
- {% if site.prod %}
1006
- <!-- Production-only features -->
1007
- <script async src="https://www.googletagmanager.com/gtag/js?id=GA_TRACKING_ID"></script>
1008
- {% else %}
1009
- <!-- Development-only features -->
1010
- <div class="dev-toolbar">Development Mode</div>
1011
- {% endif %}
1108
+ ```sh
1109
+ npm install @anydigital/eleventy-bricks
1012
1110
  ```
1013
1111
 
1014
- **Available Data:**
1015
-
1016
- - `site.year`: The current year as a number (e.g., `2026`)
1017
- - `site.prod`: Boolean indicating if running in production mode (`true` for `eleventy build`, `false` for `eleventy serve`)
1112
+ Then choose one of the following options:
1018
1113
 
1019
- **Features:**
1020
-
1021
- - Automatically updates the year value
1022
- - Detects production vs development mode based on `ELEVENTY_RUN_MODE` environment variable
1023
- - Available globally in all templates without manual setup
1024
- - No configuration required
1025
-
1026
- **Examples:**
1027
-
1028
- ```njk
1029
- {# Copyright notice #}
1030
- <p>Copyright &copy; {{ site.year }} My Site</p>
1114
+ 1. ```js {data-caption="As a plugin in eleventy.config.js (balanced)"}
1115
+ import eleventyBricksPlugin from "@anydigital/eleventy-bricks";
1031
1116
 
1032
- {# Conditional loading of analytics #}
1033
- {% if site.prod %}
1034
- <script src="/analytics.js"></script>
1035
- {% endif %}
1117
+ export default function (eleventyConfig) {
1118
+ eleventyConfig.addPlugin(eleventyBricksPlugin, { autoLinkFavicons: true });
1119
+ }
1120
+ ```
1036
1121
 
1037
- {# Different behavior in dev vs prod #}
1038
- {% if site.prod %}
1039
- <link rel="stylesheet" href="/css/styles.min.css">
1040
- {% else %}
1041
- <link rel="stylesheet" href="/css/styles.css">
1042
- <script src="/live-reload.js"></script>
1043
- {% endif %}
1044
- ```
1122
+ 2. ```js {data-caption="Individual import in eleventy.config.js (minimal)"}
1123
+ import { autoLinkFavicons } from "@anydigital/eleventy-bricks";
1045
1124
 
1046
- ### Symlinked Configuration Files
1125
+ export default function (eleventyConfig) {
1126
+ autoLinkFavicons(eleventyConfig);
1127
+ }
1128
+ ```
1047
1129
 
1048
- The package includes pre-configured starter files in `node_modules/@anydigital/eleventy-bricks/src/` that you can symlink to your project for quick setup:
1130
+ 3. ```sh {data-caption="Symlink entire eleventy.config.js (easiest)"}
1131
+ ln -s node_modules/@anydigital/eleventy-bricks/src/eleventy.config.js
1132
+ ```
1049
1133
 
1050
- Benefits of Symlinking:
1134
+ {.list-[upper-roman]}
1051
1135
 
1052
- - **Always up-to-date**: Configuration automatically updates when you upgrade the package
1053
- - **Less maintenance**: No need to manually sync configuration changes
1054
- - **Quick setup**: Get started immediately with best-practice configurations
1055
- - **Easy customization**: Override specific settings by creating your own config that imports from the symlinked version
1136
+ </details>
1056
1137
 
1057
- If you prefer to customize the configurations extensively, you can copy the files instead.
1138
+ <!--section:config-h3-->
1058
1139
 
1059
- #### eleventy.config.js
1140
+ ### Symlinked `eleventy.config.js` <sub>from https://github.com/anydigital/eleventy-bricks</sub>
1060
1141
 
1061
- A fully-configured Eleventy config file with:
1142
+ The package includes a fully-configured Eleventy config file `eleventy.config.js` that you can symlink to your project to get:
1062
1143
 
1063
1144
  - All eleventy-bricks plugins enabled
1064
1145
  - Eleventy Navigation plugin
@@ -1067,30 +1148,27 @@ A fully-configured Eleventy config file with:
1067
1148
  - YAML data support
1068
1149
  - CLI input directory support
1069
1150
  - Symlink support for development
1151
+ - _and more_
1070
1152
 
1071
- **Required dependencies:**
1072
-
1073
- ```bash
1074
- npm install @11ty/eleventy-navigation markdown-it markdown-it-anchor markdown-it-attrs js-yaml minimist
1075
- ```
1153
+ **Benefits of symlinking:**
1076
1154
 
1077
- **Optional dependencies:**
1155
+ - **Always up-to-date**: Configuration automatically updates when you upgrade the package
1156
+ - **Less maintenance**: No need to manually sync configuration changes
1157
+ - **Quick setup**: Get started immediately with best-practice configurations
1158
+ - **Easy customization**: Override specific settings by creating your own config that imports from the symlinked version
1078
1159
 
1079
- ```bash
1080
- # For the fetch filter
1081
- npm install @11ty/eleventy-fetch
1160
+ **Quick setup:**
1082
1161
 
1083
- # For table of contents generation
1084
- npm install @uncenter/eleventy-plugin-toc
1162
+ ```sh
1163
+ npm install @anydigital/eleventy-bricks
1164
+ ln -s node_modules/@anydigital/eleventy-bricks/src/eleventy.config.js
1085
1165
  ```
1086
1166
 
1087
- **Symlink to your project:**
1167
+ Done! 🎉
1088
1168
 
1089
- ```bash
1090
- ln -s node_modules/@anydigital/eleventy-bricks/src/eleventy.config.js eleventy.config.js
1091
- ```
1169
+ <!--section:cms-h4-->
1092
1170
 
1093
- #### admin/index.html
1171
+ ### Symlinked CMS
1094
1172
 
1095
1173
  A ready-to-use Sveltia CMS admin interface for content management.
1096
1174
 
@@ -1101,59 +1179,54 @@ mkdir -p admin
1101
1179
  ln -s ../node_modules/@anydigital/eleventy-bricks/src/admin/index.html admin/index.html
1102
1180
  ```
1103
1181
 
1104
- ### Using the `do` Folder Pattern
1182
+ <!--section:npm-h3-->
1183
+
1184
+ ### Reusable 11ty npm scripts <small>via npm workspace</small> <sub>from https://github.com/anydigital/eleventy-bricks</sub>
1105
1185
 
1106
1186
  This package provides a pre-configured `do` folder setup that helps organize your development workflow using npm workspaces. The `do` folder contains scripts for building and running your Eleventy project.
1107
1187
 
1108
- **Setup:**
1188
+ **Quick setup:**
1109
1189
 
1110
- 1. Create a `do` folder in your project root:
1190
+ 1. Create a simple folder, which will hold reusable npm scripts:
1111
1191
 
1112
- ```bash
1113
- mkdir do
1114
- ```
1192
+ ```sh
1193
+ mkdir do
1194
+ ```
1115
1195
 
1116
- 2. Symlink the package.json from the eleventy-bricks package:
1196
+ 2. Install https://github.com/anydigital/eleventy-bricks to reuse default 11ty scripts from there:
1117
1197
 
1118
- ```bash
1119
- ln -s node_modules/@anydigital/eleventy-bricks/src/do/package.json do/package.json
1120
- ```
1198
+ ```sh
1199
+ npm install @anydigital/eleventy-bricks
1200
+ ```
1121
1201
 
1122
- 3. Configure your root `package.json` to use npm workspaces:
1202
+ 3. Symlink the `do/package.json` containing scripts into your project's `do` folder:
1123
1203
 
1124
- ```json
1125
- {
1126
- "name": "my-project",
1127
- "workspaces": ["do"],
1128
- "scripts": {
1129
- "build": "npm -w do run build",
1130
- "start": "npm -w do run start"
1131
- }
1132
- }
1133
- ```
1204
+ ```sh
1205
+ cd do
1206
+ ln -s node_modules/@anydigital/eleventy-bricks/src/do/package.json
1207
+ ```
1134
1208
 
1135
- **Usage:**
1209
+ 4. Finally register `do` folder as npm workspace in your `package.json`, and enjoy default 11ty scripts as simple as:
1136
1210
 
1137
- Run your Eleventy project:
1211
+ ```json {data-caption="YOUR project's package.json"}
1212
+ {
1213
+ "workspaces": ["do"],
1214
+ "scripts": {
1215
+ "start": "npm -w do run start",
1216
+ "stage": "npm -w do run stage",
1217
+ "build": "npm -w do run build"
1218
+ }
1219
+ }
1220
+ ```
1138
1221
 
1139
- ```bash
1140
- npm start
1141
- ```
1142
-
1143
- Build for production:
1144
-
1145
- ```bash
1146
- npm run build
1147
- ```
1222
+ **Done!** 🎉 Now you can run:
1148
1223
 
1149
- **Available Scripts in `do` folder:**
1224
+ - `npm start` to start 11ty dev server with live reload and Tailwind watch mode
1225
+ - `npm run stage` to build and serve production-like site locally
1226
+ - `npm run build` to finally build the site for production
1227
+ - all available scripts: https://github.com/anydigital/eleventy-bricks/blob/main/src/do/package.json
1150
1228
 
1151
- - `build` - Build the site with Eleventy and minify CSS with Tailwind
1152
- - `start` - Start Eleventy dev server with live reload and Tailwind watch mode
1153
- - `stage` - Clean build and serve locally for preview
1154
- - `11ty` - Run Eleventy commands directly
1155
- - `11ty:clean` - Remove the `_site` output directory
1156
- - `tw` - Run Tailwind CSS commands
1229
+ **Example setup:** https://github.com/anydigital/sveleven
1157
1230
 
1158
1231
  **Benefits:**
1159
1232
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anydigital/eleventy-bricks",
3
- "version": "0.26.0",
3
+ "version": "0.27.1",
4
4
  "description": "A collection of helpful utilities and filters for Eleventy (11ty)",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -36,7 +36,7 @@ export default function (eleventyConfig) {
36
36
  mdAutoRawTags: true,
37
37
  autoLinkFavicons: true,
38
38
  siteData: true,
39
- filters: ["attr_set", "attr_includes", "merge", "remove_tag", "if", "attr_concat", "fetch"],
39
+ filters: ["attr_set", "attr_includes", "merge", "remove_tag", "if", "attr_concat", "fetch", "section"],
40
40
  });
41
41
  if (pluginTOC) {
42
42
  eleventyConfig.addPlugin(pluginTOC, {
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Extract a named section from content marked with HTML comments
3
+ *
4
+ * @param {string} content - The content to process
5
+ * @param {string} sectionName - The section name(s) to extract
6
+ * @returns {string} The extracted section content
7
+ */
8
+ export function section(content, sectionName) {
9
+ if (!content || typeof content !== "string") {
10
+ return content;
11
+ }
12
+
13
+ if (typeof sectionName !== "string" || !sectionName) {
14
+ return "";
15
+ }
16
+
17
+ // Normalize section name for comparison (trim whitespace)
18
+ const targetName = sectionName.trim().toLowerCase();
19
+
20
+ // Regex to match section markers with content up to the next section or end of string
21
+ // Captures: (1) section names, (2) content until next section marker or end
22
+ const sectionRegex = /<!--section:([^>]+)-->([\s\S]*?)(?=<!--section|$)/g;
23
+
24
+ let results = [];
25
+ let match;
26
+
27
+ // Find all sections
28
+ while ((match = sectionRegex.exec(content)) !== null) {
29
+ const namesStr = match[1];
30
+ const sectionContent = match[2];
31
+ const names = namesStr.split(",").map((n) => n.trim().toLowerCase());
32
+
33
+ // Check if any of the names match the target
34
+ if (names.includes(targetName)) {
35
+ results.push(sectionContent);
36
+ }
37
+ }
38
+
39
+ // Join all matching sections
40
+ return results.join("");
41
+ }
42
+
43
+ /**
44
+ * section filter - Extract a named section from content
45
+ *
46
+ * Usage in templates:
47
+ * {{ content | section('intro') }}
48
+ * {{ content | section('footer') }}
49
+ *
50
+ * Content format:
51
+ * <!--section:intro-->
52
+ * This is the intro content
53
+ * <!--section:main-->
54
+ * This is the main content
55
+ * <!--section:footer,sidebar-->
56
+ * This appears in both footer and sidebar sections
57
+ *
58
+ * @param {Object} eleventyConfig - The Eleventy configuration object
59
+ */
60
+ export function sectionFilter(eleventyConfig) {
61
+ eleventyConfig.addFilter("section", section);
62
+ }
@@ -0,0 +1,174 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert";
3
+ import { section } from "./section.js";
4
+
5
+ describe("section", () => {
6
+ it("should extract a single named section", () => {
7
+ const content = `Before
8
+ <!--section:intro-->
9
+ This is the intro
10
+ <!--section:main-->
11
+ This is the main`;
12
+
13
+ const result = section(content, "intro");
14
+ assert.strictEqual(result, "\nThis is the intro\n");
15
+ });
16
+
17
+ it("should extract section up to the next section marker", () => {
18
+ const content = `<!--section:first-->
19
+ First content
20
+ <!--section:second-->
21
+ Second content
22
+ <!--section:third-->
23
+ Third content`;
24
+
25
+ const result = section(content, "second");
26
+ assert.strictEqual(result, "\nSecond content\n");
27
+ });
28
+
29
+ it("should extract section up to EOF when no next marker", () => {
30
+ const content = `<!--section:intro-->
31
+ Intro content
32
+ <!--section:main-->
33
+ Main content that goes to the end`;
34
+
35
+ const result = section(content, "main");
36
+ assert.strictEqual(result, "\nMain content that goes to the end");
37
+ });
38
+
39
+ it("should handle section with multiple names", () => {
40
+ const content = `<!--section:header,nav-->
41
+ Shared content
42
+ <!--section:main-->
43
+ Main content`;
44
+
45
+ const resultHeader = section(content, "header");
46
+ const resultNav = section(content, "nav");
47
+
48
+ assert.strictEqual(resultHeader, "\nShared content\n");
49
+ assert.strictEqual(resultNav, "\nShared content\n");
50
+ });
51
+
52
+ it("should handle section with multiple names (spaces around commas)", () => {
53
+ const content = `<!--section:header, nav , top-->
54
+ Shared content
55
+ <!--section:main-->
56
+ Main content`;
57
+
58
+ const resultHeader = section(content, "header");
59
+ const resultNav = section(content, "nav");
60
+ const resultTop = section(content, "top");
61
+
62
+ assert.strictEqual(resultHeader, "\nShared content\n");
63
+ assert.strictEqual(resultNav, "\nShared content\n");
64
+ assert.strictEqual(resultTop, "\nShared content\n");
65
+ });
66
+
67
+ it("should return empty string for non-existent section", () => {
68
+ const content = `<!--section:intro-->
69
+ Content here
70
+ <!--section:main-->
71
+ More content`;
72
+
73
+ const result = section(content, "footer");
74
+ assert.strictEqual(result, "");
75
+ });
76
+
77
+ it("should handle empty or null input", () => {
78
+ assert.strictEqual(section("", "test"), "");
79
+ assert.strictEqual(section(null, "test"), null);
80
+ assert.strictEqual(section(undefined, "test"), undefined);
81
+ });
82
+
83
+ it("should handle missing section name", () => {
84
+ const content = `<!--section:intro-->Content`;
85
+
86
+ assert.strictEqual(section(content, ""), "");
87
+ assert.strictEqual(section(content, null), "");
88
+ assert.strictEqual(section(content, undefined), "");
89
+ });
90
+
91
+ it("should be case-insensitive for section names", () => {
92
+ const content = `<!--section:INTRO-->
93
+ Content here
94
+ <!--section:Main-->
95
+ More content`;
96
+
97
+ const result1 = section(content, "intro");
98
+ const result2 = section(content, "INTRO");
99
+ const result3 = section(content, "main");
100
+ const result4 = section(content, "MAIN");
101
+
102
+ assert.strictEqual(result1, "\nContent here\n");
103
+ assert.strictEqual(result2, "\nContent here\n");
104
+ assert.strictEqual(result3, "\nMore content");
105
+ assert.strictEqual(result4, "\nMore content");
106
+ });
107
+
108
+ it("should handle multiple sections with the same name", () => {
109
+ const content = `<!--section:note-->
110
+ First note
111
+ <!--section:main-->
112
+ Main content
113
+ <!--section:note-->
114
+ Second note
115
+ <!--section:footer-->
116
+ Footer`;
117
+
118
+ const result = section(content, "note");
119
+ assert.strictEqual(result, "\nFirst note\n\nSecond note\n");
120
+ });
121
+
122
+ it("should handle sections with no content", () => {
123
+ const content = `<!--section:empty--><!--section:main-->
124
+ Main content`;
125
+
126
+ const result = section(content, "empty");
127
+ assert.strictEqual(result, "");
128
+ });
129
+
130
+ it("should handle content before first section", () => {
131
+ const content = `Some preamble
132
+ <!--section:intro-->
133
+ Intro content`;
134
+
135
+ const result = section(content, "intro");
136
+ assert.strictEqual(result, "\nIntro content");
137
+ });
138
+
139
+ it("should handle complex real-world example", () => {
140
+ const content = `# Document Title
141
+
142
+ <!--section:summary,abstract-->
143
+ This is a summary that can be used as an abstract.
144
+ <!--section:introduction-->
145
+ This is the introduction.
146
+ <!--section:methods-->
147
+ These are the methods.
148
+ <!--section:conclusion,summary-->
149
+ This is the conclusion and also part of summary.`;
150
+
151
+ const summary = section(content, "summary");
152
+ const introduction = section(content, "introduction");
153
+ const methods = section(content, "methods");
154
+ const conclusion = section(content, "conclusion");
155
+
156
+ assert.strictEqual(
157
+ summary,
158
+ "\nThis is a summary that can be used as an abstract.\n\nThis is the conclusion and also part of summary.",
159
+ );
160
+ assert.strictEqual(introduction, "\nThis is the introduction.\n");
161
+ assert.strictEqual(methods, "\nThese are the methods.\n");
162
+ assert.strictEqual(conclusion, "\nThis is the conclusion and also part of summary.");
163
+ });
164
+
165
+ it("should handle section markers with extra whitespace", () => {
166
+ const content = `<!--section: intro -->
167
+ Content
168
+ <!--section: main -->
169
+ More`;
170
+
171
+ const result = section(content, "intro");
172
+ assert.strictEqual(result, "\nContent\n");
173
+ });
174
+ });
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { mdAutoRawTags, mdAutoNl2br, transformAutoRaw, transformNl2br } from "./transforms/markdown.js";
1
+ import { mdAutoRawTags, mdAutoNl2br, transformAutoRaw, transformNl2br } from "./processors/markdown.js";
2
2
  import {
3
3
  autoLinkFavicons,
4
4
  isPlainUrlText,
@@ -6,13 +6,14 @@ import {
6
6
  buildFaviconLink,
7
7
  transformLink,
8
8
  replaceLinksInHtml,
9
- } from "./transforms/autoLinkFavicons.js";
9
+ } from "./processors/autoLinkFavicons.js";
10
10
  import { attrSetFilter, attrSet } from "./filters/attr_set.js";
11
11
  import { attrIncludesFilter } from "./filters/attr_includes.js";
12
12
  import { mergeFilter, merge } from "./filters/merge.js";
13
13
  import { removeTagFilter, removeTag } from "./filters/remove_tag.js";
14
14
  import { ifFilter, iff } from "./filters/if.js";
15
15
  import { attrConcatFilter, attrConcat } from "./filters/attr_concat.js";
16
+ import { sectionFilter, section as sectionFn } from "./filters/section.js";
16
17
  import { siteData } from "./siteData.js";
17
18
 
18
19
  // Conditionally import fetchFilter only if @11ty/eleventy-fetch is available
@@ -36,7 +37,7 @@ try {
36
37
  * @param {boolean} options.mdAutoRawTags - Enable mdAutoRawTags preprocessor (default: false)
37
38
  * @param {boolean} options.mdAutoNl2br - Enable mdAutoNl2br for \n to <br> conversion (default: false)
38
39
  * @param {boolean} options.autoLinkFavicons - Enable autoLinkFavicons to add favicons to plain text links (default: false)
39
- * @param {Array<string>} options.filters - Array of filter names to enable: 'attr_set', 'attr_includes', 'merge', 'remove_tag', 'if', 'attr_concat', 'fetch' (default: [])
40
+ * @param {Array<string>} options.filters - Array of filter names to enable: 'attr_set', 'attr_includes', 'merge', 'remove_tag', 'if', 'attr_concat', 'section', 'fetch' (default: [])
40
41
  * @param {boolean} options.siteData - Enable site.year and site.prod global data (default: false)
41
42
  */
42
43
  export default function eleventyBricksPlugin(eleventyConfig, options = {}) {
@@ -54,6 +55,7 @@ export default function eleventyBricksPlugin(eleventyConfig, options = {}) {
54
55
  remove_tag: removeTagFilter,
55
56
  if: ifFilter,
56
57
  attr_concat: attrConcatFilter,
58
+ section: sectionFilter,
57
59
  ...(fetchFilter && { fetch: fetchFilter }),
58
60
  };
59
61
 
@@ -86,6 +88,7 @@ export {
86
88
  removeTagFilter,
87
89
  ifFilter,
88
90
  attrConcatFilter,
91
+ sectionFilter,
89
92
  fetchFilter,
90
93
  siteData,
91
94
  };
@@ -104,4 +107,5 @@ export {
104
107
  iff,
105
108
  attrConcat,
106
109
  attrSet,
110
+ sectionFn as section,
107
111
  };
File without changes
File without changes