@anydigital/eleventy-bricks 1.0.0-alpha.17 → 1.0.0-alpha.19

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.
@@ -0,0 +1,49 @@
1
+ import lodash from "@11ty/lodash-custom";
2
+ const { get } = lodash;
3
+
4
+ /**
5
+ * Core logic for filtering collection items by attribute value
6
+ *
7
+ * This function takes a collection, an attribute name, and a target value,
8
+ * and returns items where the attribute matches the target value.
9
+ * If the attribute is an array, it checks if the array includes the target value.
10
+ *
11
+ * Supports nested attribute names using dot notation (e.g., "data.tags").
12
+ *
13
+ * @param {Array} collection - The collection to filter
14
+ * @param {string} attrName - The attribute name to check (supports dot notation for nested properties)
15
+ * @param {*} targetValue - The value to match against
16
+ * @returns {Array} Filtered collection
17
+ */
18
+ export function whereIn(collection, attrName, targetValue) {
19
+ if (!collection || !Array.isArray(collection)) {
20
+ return [];
21
+ }
22
+
23
+ return collection.filter((item) => {
24
+ // Get the attribute value from the item (supports nested paths like "data.tags")
25
+ const attrValue = get(item, attrName);
26
+
27
+ // If attribute doesn't exist, skip this item
28
+ if (attrValue === undefined || attrValue === null) {
29
+ return false;
30
+ }
31
+
32
+ // If the attribute is an array, check if it includes the target value
33
+ if (Array.isArray(attrValue)) {
34
+ return attrValue.includes(targetValue);
35
+ }
36
+
37
+ // Otherwise, do a direct comparison
38
+ return attrValue === targetValue;
39
+ });
40
+ }
41
+
42
+ /**
43
+ * Registers the where_in filter with Eleventy
44
+ *
45
+ * @param {Object} eleventyConfig - The Eleventy configuration object
46
+ */
47
+ export function whereInFilter(eleventyConfig) {
48
+ eleventyConfig.addFilter("where_in", whereIn);
49
+ }
@@ -0,0 +1,148 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert";
3
+ import { whereIn, whereInFilter } from "./where_in.js";
4
+
5
+ describe("whereIn (core logic)", () => {
6
+ it("should filter items by exact attribute match", () => {
7
+ const collection = [
8
+ { category: "blog", title: "Post 1" },
9
+ { category: "news", title: "Post 2" },
10
+ { category: "blog", title: "Post 3" },
11
+ ];
12
+
13
+ const result = whereIn(collection, "category", "blog");
14
+ assert.strictEqual(result.length, 2);
15
+ assert.strictEqual(result[0].title, "Post 1");
16
+ assert.strictEqual(result[1].title, "Post 3");
17
+ });
18
+
19
+ it("should filter items when attribute is an array (includes check)", () => {
20
+ const collection = [
21
+ { tags: ["javascript", "tutorial"], title: "Post 1" },
22
+ { tags: ["python", "tutorial"], title: "Post 2" },
23
+ { tags: ["javascript", "advanced"], title: "Post 3" },
24
+ ];
25
+
26
+ const result = whereIn(collection, "tags", "javascript");
27
+ assert.strictEqual(result.length, 2);
28
+ assert.strictEqual(result[0].title, "Post 1");
29
+ assert.strictEqual(result[1].title, "Post 3");
30
+ });
31
+
32
+ it("should return empty array when collection is not an array", () => {
33
+ const result = whereIn(null, "category", "blog");
34
+ assert.strictEqual(result.length, 0);
35
+ });
36
+
37
+ it("should filter out items without the specified attribute", () => {
38
+ const collection = [
39
+ { category: "blog", title: "Post 1" },
40
+ { title: "Post 2" },
41
+ { category: "blog", title: "Post 3" },
42
+ ];
43
+
44
+ const result = whereIn(collection, "category", "blog");
45
+ assert.strictEqual(result.length, 2);
46
+ });
47
+
48
+ it("should work with attribute directly on item (not in data)", () => {
49
+ const collection = [
50
+ { category: "blog", title: "Post 1" },
51
+ { category: "news", title: "Post 2" },
52
+ { category: "blog", title: "Post 3" },
53
+ ];
54
+
55
+ const result = whereIn(collection, "category", "blog");
56
+ assert.strictEqual(result.length, 2);
57
+ });
58
+
59
+ it("should handle array that does not include target value", () => {
60
+ const collection = [
61
+ { tags: ["python", "tutorial"], title: "Post 1" },
62
+ { tags: ["ruby", "guide"], title: "Post 2" },
63
+ ];
64
+
65
+ const result = whereIn(collection, "tags", "javascript");
66
+ assert.strictEqual(result.length, 0);
67
+ });
68
+
69
+ it("should handle different value types", () => {
70
+ const collection = [
71
+ { priority: 1, title: "Post 1" },
72
+ { priority: 2, title: "Post 2" },
73
+ { priority: 1, title: "Post 3" },
74
+ ];
75
+
76
+ const result = whereIn(collection, "priority", 1);
77
+ assert.strictEqual(result.length, 2);
78
+ });
79
+
80
+ it("should support nested attribute names with dot notation", () => {
81
+ const collection = [
82
+ { data: { category: "blog" }, title: "Post 1" },
83
+ { data: { category: "news" }, title: "Post 2" },
84
+ { data: { category: "blog" }, title: "Post 3" },
85
+ ];
86
+
87
+ const result = whereIn(collection, "data.category", "blog");
88
+ assert.strictEqual(result.length, 2);
89
+ assert.strictEqual(result[0].title, "Post 1");
90
+ assert.strictEqual(result[1].title, "Post 3");
91
+ });
92
+
93
+ it("should support nested arrays with dot notation", () => {
94
+ const collection = [
95
+ { data: { tags: ["javascript", "tutorial"] }, title: "Post 1" },
96
+ { data: { tags: ["python", "tutorial"] }, title: "Post 2" },
97
+ { data: { tags: ["javascript", "advanced"] }, title: "Post 3" },
98
+ ];
99
+
100
+ const result = whereIn(collection, "data.tags", "javascript");
101
+ assert.strictEqual(result.length, 2);
102
+ assert.strictEqual(result[0].title, "Post 1");
103
+ assert.strictEqual(result[1].title, "Post 3");
104
+ });
105
+
106
+ it("should handle deeply nested paths", () => {
107
+ const collection = [
108
+ { data: { meta: { status: "published" } }, title: "Post 1" },
109
+ { data: { meta: { status: "draft" } }, title: "Post 2" },
110
+ { data: { meta: { status: "published" } }, title: "Post 3" },
111
+ ];
112
+
113
+ const result = whereIn(collection, "data.meta.status", "published");
114
+ assert.strictEqual(result.length, 2);
115
+ assert.strictEqual(result[0].title, "Post 1");
116
+ assert.strictEqual(result[1].title, "Post 3");
117
+ });
118
+
119
+ it("should return empty array when nested path does not exist", () => {
120
+ const collection = [
121
+ { data: { category: "blog" }, title: "Post 1" },
122
+ { title: "Post 2" },
123
+ ];
124
+
125
+ const result = whereIn(collection, "data.nonexistent.path", "value");
126
+ assert.strictEqual(result.length, 0);
127
+ });
128
+ });
129
+
130
+ describe("whereInFilter (Eleventy integration)", () => {
131
+ it("should register the filter with eleventyConfig", () => {
132
+ let registeredName;
133
+ let registeredFn;
134
+
135
+ const mockEleventyConfig = {
136
+ addFilter(name, fn) {
137
+ registeredName = name;
138
+ registeredFn = fn;
139
+ },
140
+ };
141
+
142
+ whereInFilter(mockEleventyConfig);
143
+
144
+ assert.strictEqual(registeredName, "where_in");
145
+ assert.strictEqual(typeof registeredFn, "function");
146
+ assert.strictEqual(registeredFn, whereIn);
147
+ });
148
+ });
package/src/index.cjs CHANGED
@@ -10,7 +10,7 @@ module.exports = async function eleventyBricksPlugin(eleventyConfig, options) {
10
10
  };
11
11
 
12
12
  // Export individual helpers for granular usage
13
- ['bricks', 'mdAutoRawTags', 'mdAutoNl2br', 'setAttrFilter', 'byAttrFilter', 'mergeFilter', 'removeTagFilter', 'siteData'].forEach(name => {
13
+ ['mdAutoRawTags', 'mdAutoNl2br', 'setAttrFilter', 'whereInFilter', 'mergeFilter', 'removeTagFilter', 'ifFilter', 'attrConcatFilter', 'siteData'].forEach(name => {
14
14
  module.exports[name] = async (eleventyConfig) => {
15
15
  const module = await import('./index.js');
16
16
  return module[name](eleventyConfig);
@@ -18,7 +18,7 @@ module.exports = async function eleventyBricksPlugin(eleventyConfig, options) {
18
18
  });
19
19
 
20
20
  // Export transform/utility functions for advanced usage
21
- ['transformAutoRaw', 'transformNl2br', 'merge', 'removeTag'].forEach(name => {
21
+ ['transformAutoRaw', 'transformNl2br', 'merge', 'removeTag', 'iff', 'attrConcat'].forEach(name => {
22
22
  module.exports[name] = async (...args) => {
23
23
  const module = await import('./index.js');
24
24
  return module[name](...args);
package/src/index.js CHANGED
@@ -1,35 +1,75 @@
1
- import { bricks } from "./bricks.js";
2
- import { mdAutoRawTags, mdAutoNl2br, transformAutoRaw, transformNl2br } from "./markdown.js";
3
- import { setAttrFilter } from "./setAttrFilter.js";
4
- import { byAttrFilter } from "./byAttrFilter.js";
5
- import { mergeFilter, merge } from "./mergeFilter.js";
6
- import { removeTagFilter, removeTag } from "./removeTagFilter.js";
1
+ import {
2
+ mdAutoRawTags,
3
+ mdAutoNl2br,
4
+ transformAutoRaw,
5
+ transformNl2br,
6
+ } from "./markdown.js";
7
+ import { setAttrFilter } from "./filters/attr.js";
8
+ import { whereInFilter } from "./filters/where_in.js";
9
+ import { mergeFilter, merge } from "./filters/merge.js";
10
+ import { removeTagFilter, removeTag } from "./filters/remove_tag.js";
11
+ import { ifFilter, iff } from "./filters/if.js";
12
+ import { attrConcatFilter, attrConcat } from "./filters/attr_concat.js";
7
13
  import { siteData } from "./siteData.js";
8
14
 
9
15
  /**
10
16
  * 11ty Bricks Plugin
11
- *
17
+ *
12
18
  * A collection of helpful utilities and filters for Eleventy (11ty).
13
19
  * Can be used as a plugin or by importing individual helpers.
14
- *
20
+ *
15
21
  * @param {Object} eleventyConfig - The Eleventy configuration object
16
22
  * @param {Object} options - Plugin options
17
- * @param {boolean} options.bricks - Enable bricks system with dependencies injection (default: false)
18
23
  * @param {boolean} options.mdAutoRawTags - Enable mdAutoRawTags preprocessor (default: false)
19
24
  * @param {boolean} options.mdAutoNl2br - Enable mdAutoNl2br for \n to <br> conversion (default: false)
20
- * @param {boolean} options.setAttrFilter - Enable setAttr filter (default: false)
21
- * @param {boolean} options.byAttrFilter - Enable byAttr filter (default: false)
22
- * @param {boolean} options.mergeFilter - Enable merge filter (default: false)
23
- * @param {boolean} options.removeTagFilter - Enable removeTag filter (default: false)
24
- * @param {boolean} options.siteData - Enable site.year and site.isProd global data (default: false)
25
+ * @param {Array<string>} options.filters - Array of filter names to enable: 'attr', 'where_in', 'merge', 'remove_tag', 'if', 'attr_concat' (default: [])
26
+ * @param {boolean} options.siteData - Enable site.year and site.prod global data (default: false)
25
27
  */
26
28
  export default function eleventyBricksPlugin(eleventyConfig, options = {}) {
27
- const plugins = { bricks, mdAutoRawTags, mdAutoNl2br, setAttrFilter, byAttrFilter, mergeFilter, removeTagFilter, siteData };
28
- Object.entries(options).forEach(([key, enabled]) => enabled && plugins[key]?.(eleventyConfig));
29
+ const plugins = {
30
+ mdAutoRawTags,
31
+ mdAutoNl2br,
32
+ siteData,
33
+ };
34
+
35
+ const filters = {
36
+ attr: setAttrFilter,
37
+ where_in: whereInFilter,
38
+ merge: mergeFilter,
39
+ remove_tag: removeTagFilter,
40
+ if: ifFilter,
41
+ attr_concat: attrConcatFilter,
42
+ };
43
+
44
+ // Handle individual plugin options
45
+ Object.entries(options).forEach(([key, enabled]) => {
46
+ if (key !== "filters" && enabled && plugins[key]) {
47
+ plugins[key](eleventyConfig);
48
+ }
49
+ });
50
+
51
+ // Handle filters array
52
+ if (Array.isArray(options.filters)) {
53
+ options.filters.forEach((filterName) => {
54
+ if (filters[filterName]) {
55
+ filters[filterName](eleventyConfig);
56
+ }
57
+ });
58
+ }
29
59
  }
30
60
 
31
61
  // Export individual helpers for granular usage
32
- export { bricks, mdAutoRawTags, mdAutoNl2br, setAttrFilter, byAttrFilter, mergeFilter, removeTagFilter, siteData };
62
+ export {
63
+ mdAutoRawTags,
64
+ mdAutoNl2br,
65
+ setAttrFilter,
66
+ whereInFilter,
67
+ mergeFilter,
68
+ removeTagFilter,
69
+ ifFilter,
70
+ attrConcatFilter,
71
+ siteData,
72
+ };
33
73
 
34
74
  // Export transform/utility functions for advanced usage
35
- export { transformAutoRaw, transformNl2br, merge, removeTag };
75
+ export { transformAutoRaw, transformNl2br, merge, removeTag, iff, attrConcat };
package/src/siteData.js CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Add site.year and site.isProd global data
3
- * - site.isProd: Boolean indicating if running in production mode (build) vs development (serve)
2
+ * Add site.year and site.prod global data
3
+ * - site.prod: Boolean indicating if running in production mode (build) vs development (serve)
4
4
  * - site.year: Sets the current year to be available in all templates as {{ site.year }}
5
5
  *
6
6
  * @param {Object} eleventyConfig - The Eleventy configuration object
7
7
  */
8
8
  export function siteData(eleventyConfig) {
9
- eleventyConfig.addGlobalData("site.isProd", () => process.env.ELEVENTY_RUN_MODE === "build");
9
+ eleventyConfig.addGlobalData("site.prod", () => process.env.ELEVENTY_RUN_MODE === "build");
10
10
  eleventyConfig.addGlobalData("site.year", () => new Date().getFullYear());
11
11
  }
12
12
 
package/src/bricks.js DELETED
@@ -1,125 +0,0 @@
1
- export function bricks(eleventyConfig) {
2
-
3
- // Brick Registry System
4
- // Global registry to track dependencies per page
5
- const brickRegistry = new Map();
6
-
7
- // Helper to get or create page registry
8
- function getPageRegistry(page) {
9
- const pageUrl = page.url || page.outputPath || 'default';
10
- if (!brickRegistry.has(pageUrl)) {
11
- brickRegistry.set(pageUrl, {
12
- dependencies: new Set(), // Raw dependencies (URLs) - categorized later
13
- inlineStyles: new Set(),
14
- inlineScripts: new Set()
15
- });
16
- }
17
- return brickRegistry.get(pageUrl);
18
- }
19
-
20
- // Clear registry before each build
21
- eleventyConfig.on("eleventy.before", async () => {
22
- brickRegistry.clear();
23
- });
24
-
25
- // brick shortcode: registers and renders a brick component
26
- eleventyConfig.addShortcode("brick", function(brickModule, ...args) {
27
- const registry = getPageRegistry(this.page);
28
-
29
- if (!brickModule) return '';
30
-
31
- // Register external dependencies (categorized later in transform)
32
- if (brickModule.dependencies) {
33
- brickModule.dependencies.forEach(dep => {
34
- registry.dependencies.add(dep);
35
- });
36
- }
37
-
38
- // Register inline styles directly from style variable
39
- if (brickModule.style && brickModule.style.trim()) {
40
- registry.inlineStyles.add(brickModule.style);
41
- }
42
-
43
- // Register inline scripts directly from script variable
44
- if (brickModule.script && brickModule.script.trim()) {
45
- registry.inlineScripts.add(brickModule.script);
46
- }
47
-
48
- // Render the brick using render() macro
49
- if (brickModule.render && typeof brickModule.render === 'function') {
50
- return brickModule.render(...args);
51
- }
52
-
53
- return '';
54
- });
55
-
56
- // bricksRegistry shortcode: outputs placeholder and base dependencies
57
- eleventyConfig.addShortcode("bricksDependencies", function(dependencies = []) {
58
- const registry = getPageRegistry(this.page);
59
-
60
- // Register root dependencies if provided (categorized later in transform)
61
- if (dependencies && Array.isArray(dependencies)) {
62
- dependencies.forEach(dep => {
63
- registry.dependencies.add(dep);
64
- });
65
- }
66
-
67
- // Return placeholder comment that will be replaced by transform
68
- return '<!-- BRICK_DEPENDENCIES_PLACEHOLDER -->';
69
- });
70
-
71
- // Transform to inject collected dependencies
72
- eleventyConfig.addTransform("injectBrickDependencies", function(content, outputPath) {
73
- if (!outputPath || !outputPath.endsWith(".html")) {
74
- return content;
75
- }
76
-
77
- const pageUrl = this.page?.url || this.page?.outputPath || outputPath;
78
- const registry = brickRegistry.get(pageUrl);
79
-
80
- if (!registry || !content.includes('<!-- BRICK_DEPENDENCIES_PLACEHOLDER -->')) {
81
- return content;
82
- }
83
-
84
- // Categorize dependencies by type
85
- const externalStyles = [];
86
- const externalScripts = [];
87
-
88
- registry.dependencies.forEach(dep => {
89
- // Categorize by type
90
- if (dep.endsWith('.css') || dep.includes('.css?')) {
91
- externalStyles.push(dep);
92
- } else if (dep.endsWith('.js') || dep.includes('.js?')) {
93
- externalScripts.push(dep);
94
- }
95
- });
96
-
97
- // Build HTML for dependencies
98
- let dependenciesHtml = '\n';
99
-
100
- // Add external CSS links
101
- externalStyles.forEach(href => {
102
- dependenciesHtml += ` <link rel="stylesheet" href="${href}">\n`;
103
- });
104
-
105
- // Add inline styles
106
- registry.inlineStyles.forEach(style => {
107
- dependenciesHtml += ` <style>${style}</style>\n`;
108
- });
109
-
110
- // Add external script links
111
- externalScripts.forEach(src => {
112
- dependenciesHtml += ` <script src="${src}"></script>\n`;
113
- });
114
-
115
- // Add inline scripts
116
- registry.inlineScripts.forEach(script => {
117
- dependenciesHtml += ` <script>${script}</script>\n`;
118
- });
119
-
120
- dependenciesHtml += ' ';
121
-
122
- // Replace placeholder with actual dependencies
123
- return content.replace('<!-- BRICK_DEPENDENCIES_PLACEHOLDER -->', dependenciesHtml);
124
- });
125
- }
@@ -1,35 +0,0 @@
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
-
@@ -1,105 +0,0 @@
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
-
@@ -1,57 +0,0 @@
1
- /**
2
- * Merge arrays or objects together
3
- *
4
- * For arrays: concatenates them together
5
- * For objects: shallow merges them (later values override earlier ones)
6
- *
7
- * @param {Array|Object} first - The first array or object
8
- * @param {...Array|Object} rest - Additional arrays or objects to merge
9
- * @returns {Array|Object} The merged result
10
- */
11
- export function merge(first, ...rest) {
12
- // If first argument is null or undefined, treat as empty
13
- if (first === null || first === undefined) {
14
- first = Array.isArray(rest[0]) ? [] : {};
15
- }
16
-
17
- // Determine if we're working with arrays or objects
18
- const isArray = Array.isArray(first);
19
-
20
- if (isArray) {
21
- // Merge arrays by concatenating
22
- return rest.reduce((acc, item) => {
23
- if (Array.isArray(item)) {
24
- return acc.concat(item);
25
- }
26
- // If item is not an array, add it as a single element
27
- return acc.concat([item]);
28
- }, [...first]);
29
- } else if (typeof first === 'object') {
30
- // Merge objects using spread operator (shallow merge)
31
- return rest.reduce((acc, item) => {
32
- if (item !== null && typeof item === 'object' && !Array.isArray(item)) {
33
- return { ...acc, ...item };
34
- }
35
- return acc;
36
- }, { ...first });
37
- }
38
-
39
- // If first is a primitive, return it as-is
40
- return first;
41
- }
42
-
43
- /**
44
- * merge filter - Merge arrays or objects together
45
- *
46
- * This filter merges arrays or objects, similar to Twig's merge filter.
47
- *
48
- * Usage in templates:
49
- * {{ array1 | merge(array2) }}
50
- * {{ array1 | merge(array2, array3) }}
51
- * {{ obj1 | merge(obj2) }}
52
- *
53
- * @param {Object} eleventyConfig - The Eleventy configuration object
54
- */
55
- export function mergeFilter(eleventyConfig) {
56
- eleventyConfig.addFilter("merge", merge);
57
- }