@anydigital/eleventy-bricks 0.28.0-alpha.2 → 0.28.0-alpha.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anydigital/eleventy-bricks",
3
- "version": "0.28.0-alpha.2",
3
+ "version": "0.28.0-alpha.4",
4
4
  "description": "A collection of helpful utilities and filters for Eleventy (11ty)",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -11,8 +11,7 @@
11
11
  }
12
12
  },
13
13
  "scripts": {
14
- "test": "node --test src/**/*.test.js",
15
- "prepublishOnly": "npm run test"
14
+ "test": "node --test src/**/*.test.js"
16
15
  },
17
16
  "keywords": [
18
17
  "11ty",
@@ -9,7 +9,7 @@
9
9
  "11ty": "cd ../ && NODE_OPTIONS='--preserve-symlinks' eleventy",
10
10
  "11ty:clean": "rm -r ../_site",
11
11
  "11ty:debug": "DEBUG=* npm run 11ty --",
12
- "tw": "tailwindcss -i ../src/_theme/styles.css -o ../_site/styles.css",
12
+ "tw": "tailwindcss -i ../_includes/styles.css -o ../_site/styles.css",
13
13
  "tw:debug": "DEBUG=* npm run tw --"
14
14
  }
15
15
  }
@@ -1,5 +1,3 @@
1
- /* CLI */
2
- import minimist from "minimist";
3
1
  /* Plugins */
4
2
  import { RenderPlugin } from "@11ty/eleventy";
5
3
  import eleventyBricksPlugin from "@anydigital/eleventy-bricks";
@@ -46,9 +44,13 @@ import yaml from "js-yaml";
46
44
  * @returns {Object} The Eleventy configuration object
47
45
  */
48
46
  export default function (eleventyConfig) {
49
- /* CLI support */
50
- const argv = minimist(process.argv.slice(2));
51
- const inputDir = argv.input || ".";
47
+ const inputDir = eleventyConfig.directories.input;
48
+
49
+ /* Jekyll parity */
50
+ eleventyConfig.addPassthroughCopy("assets");
51
+ eleventyConfig.addGlobalData("layout", "default");
52
+ eleventyConfig.setLiquidOptions({ dynamicPartials: false }); // allows unquoted Jekyll-style includes
53
+ eleventyConfig.addFilter("relative_url", (content) => content);
52
54
 
53
55
  /* Plugins */
54
56
  eleventyConfig.addPlugin(RenderPlugin);
@@ -58,7 +60,18 @@ export default function (eleventyConfig) {
58
60
  mdAutoRawTags: true,
59
61
  autoLinkFavicons: true,
60
62
  siteData: true,
61
- filters: ["attr_set", "attr_includes", "merge", "remove_tag", "if", "attr_concat", "fetch", "section", "strip_tag"],
63
+ filters: [
64
+ "attr_set",
65
+ "attr_includes",
66
+ "merge",
67
+ "remove_tag",
68
+ "if",
69
+ "attr_concat",
70
+ "fetch",
71
+ "section",
72
+ "strip_tag",
73
+ "unindent",
74
+ ],
62
75
  });
63
76
  if (pluginTOC) {
64
77
  eleventyConfig.addPlugin(pluginTOC, {
@@ -68,9 +81,6 @@ export default function (eleventyConfig) {
68
81
  }
69
82
 
70
83
  /* Libraries */
71
- eleventyConfig.setLiquidOptions({
72
- dynamicPartials: false, // This allows unquoted includes like Jekyll
73
- });
74
84
  let md = markdownIt({
75
85
  html: true,
76
86
  linkify: true,
@@ -86,14 +96,13 @@ export default function (eleventyConfig) {
86
96
  eleventyConfig.addFilter("markdownify", (content) => md.render(String(content ?? "")));
87
97
 
88
98
  /* Data */
89
- eleventyConfig.addGlobalData("layout", "default"); // Jekyll parity
90
99
  eleventyConfig.addDataExtension("yml", (contents) => yaml.load(contents));
91
100
 
92
101
  /* Build */
93
102
  eleventyConfig.addPassthroughCopy(
94
103
  {
95
- "src/_public": ".",
96
- ...(inputDir !== "src" && { [`${inputDir}/_public`]: "." }),
104
+ _public: ".",
105
+ ...(inputDir !== "." && { [`${inputDir}/_public`]: "." }),
97
106
  },
98
107
  { expand: true }, // This follows/resolves symbolic links
99
108
  );
@@ -101,12 +110,4 @@ export default function (eleventyConfig) {
101
110
  /* Dev tools */
102
111
  // Follow symlinks in Chokidar used by 11ty to watch files
103
112
  eleventyConfig.setChokidarConfig({ followSymlinks: true });
104
-
105
- /* Config */
106
- return {
107
- dir: {
108
- input: inputDir,
109
- // includes: "_theme",
110
- },
111
- };
112
113
  }
@@ -18,7 +18,7 @@ export function fetchFilter(eleventyConfig) {
18
18
  }
19
19
 
20
20
  // Get the input directory from Eleventy config
21
- const inputDir = eleventyConfig.dir?.input || ".";
21
+ const inputDir = this.eleventy.directories.input;
22
22
 
23
23
  // Check if it's a URL or local path
24
24
  const isUrl = url.startsWith("http://") || url.startsWith("https://");
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Remove the minimal common indentation from a multi-line string
3
+ *
4
+ * Finds the smallest leading-whitespace count across all non-empty lines
5
+ * and strips that many characters from the beginning of every line.
6
+ *
7
+ * @param {string} content - The input string
8
+ * @returns {string} The unindented string
9
+ */
10
+ export function unindent(content) {
11
+ const lines = String(content ?? "").split("\n");
12
+ const minIndent = Math.min(...lines.filter((l) => l.trim()).map((l) => l.match(/^(\s*)/)[1].length));
13
+ return lines.map((l) => l.slice(minIndent)).join("\n");
14
+ }
15
+
16
+ /**
17
+ * unindent filter - Remove minimal common indentation
18
+ *
19
+ * Strips the smallest leading-whitespace indent shared by all non-empty
20
+ * lines, useful for dedenting captured or indented template blocks.
21
+ *
22
+ * Usage in templates:
23
+ * {{ content | unindent }}
24
+ *
25
+ * @param {Object} eleventyConfig - The Eleventy configuration object
26
+ */
27
+ export function unindentFilter(eleventyConfig) {
28
+ eleventyConfig.addFilter("unindent", unindent);
29
+ }
@@ -0,0 +1,49 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert";
3
+ import { unindent } from "./unindent.js";
4
+
5
+ test("unindent - removes common leading spaces", () => {
6
+ const input = " hello\n world";
7
+ assert.strictEqual(unindent(input), "hello\nworld");
8
+ });
9
+
10
+ test("unindent - removes common leading tabs", () => {
11
+ const input = "\tfoo\n\tbar";
12
+ assert.strictEqual(unindent(input), "foo\nbar");
13
+ });
14
+
15
+ test("unindent - preserves relative indentation", () => {
16
+ const input = " if true\n inner\n end";
17
+ assert.strictEqual(unindent(input), "if true\n inner\nend");
18
+ });
19
+
20
+ test("unindent - ignores blank lines when computing min indent", () => {
21
+ const input = " line1\n\n line2";
22
+ assert.strictEqual(unindent(input), "line1\n\nline2");
23
+ });
24
+
25
+ test("unindent - does nothing when already at zero indent", () => {
26
+ const input = "hello\nworld";
27
+ assert.strictEqual(unindent(input), "hello\nworld");
28
+ });
29
+
30
+ test("unindent - handles single line", () => {
31
+ assert.strictEqual(unindent(" hello"), "hello");
32
+ });
33
+
34
+ test("unindent - handles null input", () => {
35
+ assert.strictEqual(unindent(null), "");
36
+ });
37
+
38
+ test("unindent - handles undefined input", () => {
39
+ assert.strictEqual(unindent(undefined), "");
40
+ });
41
+
42
+ test("unindent - handles empty string", () => {
43
+ assert.strictEqual(unindent(""), "");
44
+ });
45
+
46
+ test("unindent - mixed indent levels, strips only the minimum", () => {
47
+ const input = " a\n b\n c";
48
+ assert.strictEqual(unindent(input), "a\n b\nc");
49
+ });
package/src/index.js CHANGED
@@ -15,6 +15,7 @@ import { stripTagFilter, stripTag } from "./filters/strip_tag.js";
15
15
  import { ifFilter, iff } from "./filters/if.js";
16
16
  import { attrConcatFilter, attrConcat } from "./filters/attr_concat.js";
17
17
  import { sectionFilter, section as sectionFn } from "./filters/section.js";
18
+ import { unindentFilter, unindent } from "./filters/unindent.js";
18
19
  import { siteData } from "./siteData.js";
19
20
 
20
21
  // Conditionally import fetchFilter only if @11ty/eleventy-fetch is available
@@ -58,6 +59,7 @@ export default function eleventyBricksPlugin(eleventyConfig, options = {}) {
58
59
  if: ifFilter,
59
60
  attr_concat: attrConcatFilter,
60
61
  section: sectionFilter,
62
+ unindent: unindentFilter,
61
63
  ...(fetchFilter && { fetch: fetchFilter }),
62
64
  };
63
65
 
@@ -92,6 +94,7 @@ export {
92
94
  ifFilter,
93
95
  attrConcatFilter,
94
96
  sectionFilter,
97
+ unindentFilter,
95
98
  fetchFilter,
96
99
  siteData,
97
100
  };
@@ -112,4 +115,5 @@ export {
112
115
  attrConcat,
113
116
  attrSet,
114
117
  sectionFn as section,
118
+ unindent,
115
119
  };
@@ -36,21 +36,12 @@ export function cleanLinkText(linkText, domain) {
36
36
  export function buildFaviconLink(attrs, domain, text) {
37
37
  let updatedAttrs = attrs;
38
38
 
39
- // Check if attrs already contains a class attribute
40
- if (/class\s*=\s*["']/.test(attrs)) {
41
- // Append whitespace-nowrap to existing class
42
- updatedAttrs = attrs.replace(/class\s*=\s*["']([^"']*)["']/i, 'class="$1 whitespace-nowrap"');
43
- } else {
44
- // Add new class attribute
45
- updatedAttrs = attrs + ' class="whitespace-nowrap"';
46
- }
47
-
48
39
  // Check if attrs already contains a target attribute
49
40
  if (!/target\s*=/.test(attrs)) {
50
41
  updatedAttrs = updatedAttrs + ' target="_blank"';
51
42
  }
52
43
 
53
- return `<a ${updatedAttrs}><i><img src="https://www.google.com/s2/favicons?domain=${domain}&sz=32"></i><span>${text}</span></a>`;
44
+ return `<a ${updatedAttrs}><i><img src="https://www.google.com/s2/favicons?domain=${domain}&sz=64"></i><span>${text}</span></a>`;
54
45
  }
55
46
 
56
47
  /**
@@ -90,7 +90,7 @@ describe("buildFaviconLink", () => {
90
90
  const result = buildFaviconLink('href="https://example.com/docs"', "example.com", "/docs");
91
91
  assert.equal(
92
92
  result,
93
- '<a href="https://example.com/docs" class="whitespace-nowrap" target="_blank"><i><img src="https://www.google.com/s2/favicons?domain=example.com&sz=32"></i><span>/docs</span></a>',
93
+ '<a href="https://example.com/docs" class="whitespace-nowrap" target="_blank"><i><img src="https://www.google.com/s2/favicons?domain=example.com&sz=64"></i><span>/docs</span></a>',
94
94
  );
95
95
  });
96
96
 
@@ -98,13 +98,13 @@ describe("buildFaviconLink", () => {
98
98
  const result = buildFaviconLink('href="https://example.com" class="link"', "example.com", "text");
99
99
  assert.equal(
100
100
  result,
101
- '<a href="https://example.com" class="link whitespace-nowrap" target="_blank"><i><img src="https://www.google.com/s2/favicons?domain=example.com&sz=32"></i><span>text</span></a>',
101
+ '<a href="https://example.com" class="link whitespace-nowrap" target="_blank"><i><img src="https://www.google.com/s2/favicons?domain=example.com&sz=64"></i><span>text</span></a>',
102
102
  );
103
103
  });
104
104
 
105
- it("should use sz=32 parameter for favicon size", () => {
105
+ it("should use sz=64 parameter for favicon size", () => {
106
106
  const result = buildFaviconLink('href="https://example.com"', "example.com", "text");
107
- assert.match(result, /sz=32/);
107
+ assert.match(result, /sz=64/);
108
108
  });
109
109
 
110
110
  it("should wrap img in <i> tag", () => {
@@ -116,7 +116,7 @@ describe("buildFaviconLink", () => {
116
116
  const result = buildFaviconLink('href="https://github.com/repo"', "github.com", "/repo");
117
117
  assert.equal(
118
118
  result,
119
- '<a href="https://github.com/repo" class="whitespace-nowrap" target="_blank"><i><img src="https://www.google.com/s2/favicons?domain=github.com&sz=32"></i><span>/repo</span></a>',
119
+ '<a href="https://github.com/repo" class="whitespace-nowrap" target="_blank"><i><img src="https://www.google.com/s2/favicons?domain=github.com&sz=64"></i><span>/repo</span></a>',
120
120
  );
121
121
  });
122
122
 
@@ -136,7 +136,7 @@ describe("transformLink", () => {
136
136
  );
137
137
  assert.match(
138
138
  result,
139
- /<i><img src="https:\/\/www\.google\.com\/s2\/favicons\?domain=example\.com&sz=32"><\/i><span>\/docs<\/span>/,
139
+ /<i><img src="https:\/\/www\.google\.com\/s2\/favicons\?domain=example\.com&sz=64"><\/i><span>\/docs<\/span>/,
140
140
  );
141
141
  });
142
142
 
@@ -395,7 +395,7 @@ describe("replaceLinksInHtml", () => {
395
395
  const html = '<a href="https://example.com/docs">https://example.com/docs</a>';
396
396
  const result = replaceLinksInHtml(html, transformLink);
397
397
  // transformLink should add favicon for plain URL links
398
- assert.match(result, /<img src="https:\/\/www\.google\.com\/s2\/favicons\?domain=example\.com&sz=32">/);
398
+ assert.match(result, /<img src="https:\/\/www\.google\.com\/s2\/favicons\?domain=example\.com&sz=64">/);
399
399
  assert.match(result, /<span>\/docs<\/span><\/a>/);
400
400
  });
401
401