@anyblades/eleventy-blades 0.28.0-beta.2
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/.prettierrc.json +3 -0
- package/LICENSE +21 -0
- package/README.md +133 -0
- package/package.json +46 -0
- package/src/admin/index.html +12 -0
- package/src/do/README.md +53 -0
- package/src/do/package.json +15 -0
- package/src/eleventy.config.js +178 -0
- package/src/filters/attr_concat.js +107 -0
- package/src/filters/attr_concat.test.js +205 -0
- package/src/filters/attr_includes.js +65 -0
- package/src/filters/attr_includes.test.js +145 -0
- package/src/filters/attr_set.js +42 -0
- package/src/filters/attr_set.test.js +71 -0
- package/src/filters/fetch.js +118 -0
- package/src/filters/if.js +79 -0
- package/src/filters/if.test.js +63 -0
- package/src/filters/merge.js +78 -0
- package/src/filters/merge.test.js +51 -0
- package/src/filters/remove_tag.js +70 -0
- package/src/filters/remove_tag.test.js +60 -0
- package/src/filters/section.js +125 -0
- package/src/filters/section.test.js +174 -0
- package/src/filters/strip_tag.js +83 -0
- package/src/filters/strip_tag.test.js +74 -0
- package/src/filters/unindent.js +35 -0
- package/src/filters/unindent.test.js +49 -0
- package/src/index.js +122 -0
- package/src/processors/autoLinkFavicons.js +147 -0
- package/src/processors/autoLinkFavicons.test.js +452 -0
- package/src/processors/markdown.js +79 -0
- package/src/processors/markdown.test.js +207 -0
- package/src/siteData.js +25 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// <!--section:code-->```js
|
|
2
|
+
/**
|
|
3
|
+
* if utility function - Ternary/conditional helper
|
|
4
|
+
*
|
|
5
|
+
* Returns trueValue if condition is truthy, otherwise returns falseValue.
|
|
6
|
+
* Similar to Nunjucks' inline if: `value if condition else other_value`
|
|
7
|
+
*
|
|
8
|
+
* @param {*} trueValue - The value to return if condition is truthy
|
|
9
|
+
* @param {*} condition - The condition to evaluate
|
|
10
|
+
* @param {*} falseValue - The value to return if condition is falsy (default: empty string)
|
|
11
|
+
* @returns {*} Either trueValue or falseValue based on condition
|
|
12
|
+
*/
|
|
13
|
+
export function iff(trueValue, condition, falseValue = "") {
|
|
14
|
+
// Treat empty objects {} as falsy
|
|
15
|
+
if (condition && typeof condition === "object" && !Array.isArray(condition) && Object.keys(condition).length === 0) {
|
|
16
|
+
return falseValue;
|
|
17
|
+
}
|
|
18
|
+
return !!condition ? trueValue : falseValue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* if filter - Inline conditional/ternary operator for templates
|
|
23
|
+
*
|
|
24
|
+
* This filter provides a simple inline if/else similar to Nunjucks.
|
|
25
|
+
*
|
|
26
|
+
* Usage in Liquid templates:
|
|
27
|
+
* {{ "Active" | if: isActive, "Inactive" }}
|
|
28
|
+
* {{ "Yes" | if: condition }}
|
|
29
|
+
* {{ someValue | if: test, otherValue }}
|
|
30
|
+
*
|
|
31
|
+
* @param {Object} eleventyConfig - The Eleventy configuration object
|
|
32
|
+
*/
|
|
33
|
+
export function ifFilter(eleventyConfig) {
|
|
34
|
+
eleventyConfig.addFilter("if", iff);
|
|
35
|
+
}
|
|
36
|
+
/*```
|
|
37
|
+
|
|
38
|
+
<!--section:docs-->
|
|
39
|
+
### `if`
|
|
40
|
+
|
|
41
|
+
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, it is especially useful in `.liquid` templates.
|
|
42
|
+
|
|
43
|
+
**Features:**
|
|
44
|
+
|
|
45
|
+
- Returns `trueValue` if condition is truthy, otherwise returns `falseValue`
|
|
46
|
+
- Treats empty objects `{}` as falsy
|
|
47
|
+
- Default `falseValue` is an empty string if not provided
|
|
48
|
+
- Works with any data type for values
|
|
49
|
+
|
|
50
|
+
**Examples:** <!-- @TODO: better examples -->
|
|
51
|
+
|
|
52
|
+
```jinja2
|
|
53
|
+
{# Basic usage (defaults to empty string) #}
|
|
54
|
+
<div class="{{ 'active' | if: isActive | default: 'inactive' }}">Status</div>
|
|
55
|
+
|
|
56
|
+
{# Toggle CSS classes #}
|
|
57
|
+
<button class="{{ 'btn-primary' | if: isPrimary | default: 'btn-secondary' }}">
|
|
58
|
+
Click me
|
|
59
|
+
</button>
|
|
60
|
+
|
|
61
|
+
{# Display different text #}
|
|
62
|
+
<p>{{ 'Online' | if: user.isOnline, 'Offline' }}</p>
|
|
63
|
+
|
|
64
|
+
{# Use with boolean values #}
|
|
65
|
+
{% set isEnabled = true %}
|
|
66
|
+
<div>{{ 'Enabled' | if: isEnabled, 'Disabled' }}</div>
|
|
67
|
+
|
|
68
|
+
{# Conditional attribute values #}
|
|
69
|
+
<input type="checkbox" {{ 'checked' | if: isChecked }}>
|
|
70
|
+
|
|
71
|
+
{# With numeric values #}
|
|
72
|
+
<span class="{{ 'has-items' | if: items.length }}">
|
|
73
|
+
{{ items.length }} items
|
|
74
|
+
</span>
|
|
75
|
+
|
|
76
|
+
{# Chain with other filters #}
|
|
77
|
+
{% set cssClass = 'featured' | if: post.featured | upper %}
|
|
78
|
+
```
|
|
79
|
+
<!--section--> */
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { iff } from "./if.js";
|
|
4
|
+
|
|
5
|
+
test("iff returns trueValue when condition is truthy", () => {
|
|
6
|
+
assert.strictEqual(iff("yes", true, "no"), "yes");
|
|
7
|
+
assert.strictEqual(iff("yes", 1, "no"), "yes");
|
|
8
|
+
assert.strictEqual(iff("yes", "truthy", "no"), "yes");
|
|
9
|
+
assert.strictEqual(iff("yes", { a: 1 }, "no"), "yes"); // non-empty object is truthy
|
|
10
|
+
assert.strictEqual(iff("yes", [], "no"), "yes");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("iff returns falseValue when condition is falsy", () => {
|
|
14
|
+
assert.strictEqual(iff("yes", false, "no"), "no");
|
|
15
|
+
assert.strictEqual(iff("yes", 0, "no"), "no");
|
|
16
|
+
assert.strictEqual(iff("yes", "", "no"), "no");
|
|
17
|
+
assert.strictEqual(iff("yes", null, "no"), "no");
|
|
18
|
+
assert.strictEqual(iff("yes", undefined, "no"), "no");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("iff treats empty objects as falsy", () => {
|
|
22
|
+
assert.strictEqual(iff("yes", {}, "no"), "no");
|
|
23
|
+
assert.strictEqual(iff("yes", {}, ""), "");
|
|
24
|
+
assert.strictEqual(iff(100, {}, 200), 200);
|
|
25
|
+
|
|
26
|
+
// Non-empty objects should still be truthy
|
|
27
|
+
assert.strictEqual(iff("yes", { a: 1 }, "no"), "yes");
|
|
28
|
+
assert.strictEqual(iff("yes", { nested: {} }, "no"), "yes");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("iff returns falseValue when condition is undefined", () => {
|
|
32
|
+
assert.strictEqual(iff("yes", undefined, "no"), "no");
|
|
33
|
+
assert.strictEqual(iff("yes", undefined), "");
|
|
34
|
+
assert.strictEqual(iff(100, undefined, 200), 200);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("iff returns empty string as default falseValue", () => {
|
|
38
|
+
assert.strictEqual(iff("yes", false), "");
|
|
39
|
+
assert.strictEqual(iff("yes", 0), "");
|
|
40
|
+
assert.strictEqual(iff("yes", null), "");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("iff works with various value types", () => {
|
|
44
|
+
assert.strictEqual(iff(100, true, 200), 100);
|
|
45
|
+
assert.strictEqual(iff(100, false, 200), 200);
|
|
46
|
+
|
|
47
|
+
const obj1 = { a: 1 };
|
|
48
|
+
const obj2 = { b: 2 };
|
|
49
|
+
assert.strictEqual(iff(obj1, true, obj2), obj1);
|
|
50
|
+
assert.strictEqual(iff(obj1, false, obj2), obj2);
|
|
51
|
+
|
|
52
|
+
const arr1 = [1, 2];
|
|
53
|
+
const arr2 = [3, 4];
|
|
54
|
+
assert.strictEqual(iff(arr1, true, arr2), arr1);
|
|
55
|
+
assert.strictEqual(iff(arr1, false, arr2), arr2);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("iff evaluates condition correctly without type coercion confusion", () => {
|
|
59
|
+
// Common edge cases
|
|
60
|
+
assert.strictEqual(iff("yes", "false", "no"), "yes"); // string 'false' is truthy
|
|
61
|
+
assert.strictEqual(iff("yes", "0", "no"), "yes"); // string '0' is truthy
|
|
62
|
+
assert.strictEqual(iff("yes", NaN, "no"), "no"); // NaN is falsy
|
|
63
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// <!--section:code-->```js
|
|
2
|
+
/**
|
|
3
|
+
* Merge objects together
|
|
4
|
+
*
|
|
5
|
+
* Shallow merges objects (later values override earlier ones)
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} first - The first object
|
|
8
|
+
* @param {...Object} rest - Additional objects to merge
|
|
9
|
+
* @returns {Object} The merged result
|
|
10
|
+
*/
|
|
11
|
+
export function merge(first, ...rest) {
|
|
12
|
+
// If first argument is null or undefined, treat as empty object
|
|
13
|
+
if (first === null || first === undefined) {
|
|
14
|
+
first = {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Only support objects
|
|
18
|
+
if (typeof first === "object" && !Array.isArray(first)) {
|
|
19
|
+
// Merge objects using spread operator (shallow merge)
|
|
20
|
+
return rest.reduce(
|
|
21
|
+
(acc, item) => {
|
|
22
|
+
if (item !== null && typeof item === "object" && !Array.isArray(item)) {
|
|
23
|
+
return { ...acc, ...item };
|
|
24
|
+
}
|
|
25
|
+
return acc;
|
|
26
|
+
},
|
|
27
|
+
{ ...first },
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// If first is not an object, return empty object
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* merge filter - Merge objects together
|
|
37
|
+
*
|
|
38
|
+
* This filter merges objects, similar to Twig's merge filter.
|
|
39
|
+
*
|
|
40
|
+
* Usage in templates:
|
|
41
|
+
* {{ obj1 | merge(obj2) }}
|
|
42
|
+
* {{ obj1 | merge(obj2, obj3) }}
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} eleventyConfig - The Eleventy configuration object
|
|
45
|
+
*/
|
|
46
|
+
export function mergeFilter(eleventyConfig) {
|
|
47
|
+
eleventyConfig.addFilter("merge", merge);
|
|
48
|
+
}
|
|
49
|
+
/*```
|
|
50
|
+
|
|
51
|
+
<!--section:docs-->
|
|
52
|
+
### `merge`
|
|
53
|
+
|
|
54
|
+
A filter that merges arrays or objects together, similar to Twig's merge filter. For arrays, it concatenates them. For objects, it performs a shallow merge where later values override earlier ones.
|
|
55
|
+
|
|
56
|
+
**Why use this?** When working with data in templates, you often need to combine multiple arrays or objects. The `merge` filter provides a clean way to merge data structures without writing custom JavaScript, making it easy to combine collections, merge configuration objects, or aggregate data from multiple sources.
|
|
57
|
+
|
|
58
|
+
**Examples:** <!-- @TODO: better examples -->
|
|
59
|
+
|
|
60
|
+
```jinja2
|
|
61
|
+
{# Merge configuration objects #}
|
|
62
|
+
{% set defaultConfig = { theme: 'light', lang: 'en' } %}
|
|
63
|
+
{% set userConfig = { theme: 'dark' } %}
|
|
64
|
+
{% set finalConfig = defaultConfig | merge(userConfig) %}
|
|
65
|
+
|
|
66
|
+
{# Result: { theme: 'dark', lang: 'en' } #}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```jinja2
|
|
70
|
+
{# Merge page metadata with defaults #}
|
|
71
|
+
{% set defaultMeta = {
|
|
72
|
+
author: 'Site Admin',
|
|
73
|
+
category: 'general',
|
|
74
|
+
comments: false
|
|
75
|
+
} %}
|
|
76
|
+
{% set pageMeta = defaultMeta | merge(page.data) %}
|
|
77
|
+
```
|
|
78
|
+
<!--section--> */
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { merge } from './merge.js';
|
|
4
|
+
|
|
5
|
+
test('merge - merges two objects', () => {
|
|
6
|
+
const result = merge({ a: 1, b: 2 }, { c: 3, d: 4 });
|
|
7
|
+
assert.deepStrictEqual(result, { a: 1, b: 2, c: 3, d: 4 });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('merge - merges objects with override', () => {
|
|
11
|
+
const result = merge({ a: 1, b: 2 }, { b: 3, c: 4 });
|
|
12
|
+
assert.deepStrictEqual(result, { a: 1, b: 3, c: 4 });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('merge - merges multiple objects', () => {
|
|
16
|
+
const result = merge({ a: 1 }, { b: 2 }, { c: 3 });
|
|
17
|
+
assert.deepStrictEqual(result, { a: 1, b: 2, c: 3 });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('merge - handles null first argument', () => {
|
|
21
|
+
const result = merge(null, { a: 1 });
|
|
22
|
+
assert.deepStrictEqual(result, { a: 1 });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('merge - handles undefined first argument', () => {
|
|
26
|
+
const result = merge(undefined, { a: 1 });
|
|
27
|
+
assert.deepStrictEqual(result, { a: 1 });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('merge - does not modify original objects', () => {
|
|
31
|
+
const original = { a: 1 };
|
|
32
|
+
const result = merge(original, { b: 2 });
|
|
33
|
+
|
|
34
|
+
assert.deepStrictEqual(original, { a: 1 });
|
|
35
|
+
assert.deepStrictEqual(result, { a: 1, b: 2 });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('merge - returns empty object for arrays', () => {
|
|
39
|
+
const result = merge([1, 2], [3, 4]);
|
|
40
|
+
assert.deepStrictEqual(result, {});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('merge - returns empty object for primitives', () => {
|
|
44
|
+
const result = merge('string', { a: 1 });
|
|
45
|
+
assert.deepStrictEqual(result, {});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('merge - ignores non-object arguments in rest', () => {
|
|
49
|
+
const result = merge({ a: 1 }, 'string', { b: 2 }, null, { c: 3 });
|
|
50
|
+
assert.deepStrictEqual(result, { a: 1, b: 2, c: 3 });
|
|
51
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// <!--section:code-->```js
|
|
2
|
+
/**
|
|
3
|
+
* Remove specified HTML element from provided HTML
|
|
4
|
+
*
|
|
5
|
+
* @param {string} html - The HTML content to process
|
|
6
|
+
* @param {string} tagName - The tag name to remove
|
|
7
|
+
* @returns {string} The HTML with the specified tag removed
|
|
8
|
+
*/
|
|
9
|
+
export function removeTag(html, tagName) {
|
|
10
|
+
if (!html || typeof html !== "string") {
|
|
11
|
+
return html;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (typeof tagName !== "string" || !tagName) {
|
|
15
|
+
return html;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Escape special regex characters in tag name
|
|
19
|
+
const escapedTag = tagName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20
|
+
|
|
21
|
+
// Remove opening and closing tags along with their content
|
|
22
|
+
// This regex matches: <tag attributes>content</tag>
|
|
23
|
+
const regex = new RegExp(`<${escapedTag}(?:\\s[^>]*)?>.*?<\\/${escapedTag}>`, "gis");
|
|
24
|
+
let result = html.replace(regex, "");
|
|
25
|
+
|
|
26
|
+
// Also remove self-closing tags: <tag />
|
|
27
|
+
const selfClosingRegex = new RegExp(`<${escapedTag}(?:\\s[^>]*)?\\s*\\/?>`, "gi");
|
|
28
|
+
result = result.replace(selfClosingRegex, "");
|
|
29
|
+
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* remove_tag filter - Remove specified HTML element from provided HTML
|
|
35
|
+
*
|
|
36
|
+
* Usage in templates:
|
|
37
|
+
* {{ htmlContent | remove_tag('script') }}
|
|
38
|
+
*
|
|
39
|
+
* @param {Object} eleventyConfig - The Eleventy configuration object
|
|
40
|
+
*/
|
|
41
|
+
export function removeTagFilter(eleventyConfig) {
|
|
42
|
+
eleventyConfig.addFilter("remove_tag", removeTag);
|
|
43
|
+
}
|
|
44
|
+
/*```
|
|
45
|
+
|
|
46
|
+
<!--section:docs-->
|
|
47
|
+
### `remove_tag`
|
|
48
|
+
|
|
49
|
+
A filter that removes a specified HTML element from provided HTML content. It removes the tag along with its content, including self-closing tags.
|
|
50
|
+
|
|
51
|
+
**Why use this?** When working with content from external sources or user-generated content, you may need to strip certain HTML tags for security or presentation purposes. The `remove_tag` filter provides a simple way to remove unwanted tags like `<script>`, `<style>`, or any other HTML elements from your content.
|
|
52
|
+
|
|
53
|
+
**Features:**
|
|
54
|
+
|
|
55
|
+
- Removes both opening and closing tags along with their content
|
|
56
|
+
- Handles self-closing tags (e.g., `<br />`, `<img />`)
|
|
57
|
+
- Handles tags with attributes
|
|
58
|
+
- Case-insensitive matching
|
|
59
|
+
- Non-destructive: Returns new string, doesn't modify original
|
|
60
|
+
|
|
61
|
+
**Security note:** 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.
|
|
62
|
+
|
|
63
|
+
#### Example: Remove all script tags from content <!-- @TODO: better examples -->
|
|
64
|
+
|
|
65
|
+
```jinja2
|
|
66
|
+
{% set cleanContent = htmlContent | remove_tag('script') %}
|
|
67
|
+
|
|
68
|
+
{{ cleanContent | safe }}
|
|
69
|
+
```
|
|
70
|
+
<!--section--> */
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { removeTag } from './remove_tag.js';
|
|
4
|
+
|
|
5
|
+
describe('removeTag', () => {
|
|
6
|
+
it('should remove a single tag with content', () => {
|
|
7
|
+
const html = '<div>Keep this</div><script>Remove this</script><p>Keep this too</p>';
|
|
8
|
+
const result = removeTag(html, 'script');
|
|
9
|
+
|
|
10
|
+
assert.strictEqual(result, '<div>Keep this</div><p>Keep this too</p>');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should remove multiple instances of the same tag', () => {
|
|
14
|
+
const html = '<p>First</p><script>One</script><p>Second</p><script>Two</script><p>Third</p>';
|
|
15
|
+
const result = removeTag(html, 'script');
|
|
16
|
+
|
|
17
|
+
assert.strictEqual(result, '<p>First</p><p>Second</p><p>Third</p>');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should handle tags with attributes', () => {
|
|
21
|
+
const html = '<div>Keep</div><script type="text/javascript" src="file.js">Code</script><p>Keep</p>';
|
|
22
|
+
const result = removeTag(html, 'script');
|
|
23
|
+
|
|
24
|
+
assert.strictEqual(result, '<div>Keep</div><p>Keep</p>');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should handle self-closing tags', () => {
|
|
28
|
+
const html = '<div>Keep</div><br /><p>Keep</p>';
|
|
29
|
+
const result = removeTag(html, 'br');
|
|
30
|
+
|
|
31
|
+
assert.strictEqual(result, '<div>Keep</div><p>Keep</p>');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should return original HTML if tag does not exist', () => {
|
|
35
|
+
const html = '<div>Keep this</div><p>Keep this too</p>';
|
|
36
|
+
const result = removeTag(html, 'script');
|
|
37
|
+
|
|
38
|
+
assert.strictEqual(result, html);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should handle empty or null input', () => {
|
|
42
|
+
assert.strictEqual(removeTag('', 'script'), '');
|
|
43
|
+
assert.strictEqual(removeTag(null, 'script'), null);
|
|
44
|
+
assert.strictEqual(removeTag(undefined, 'script'), undefined);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should be case-insensitive', () => {
|
|
48
|
+
const html = '<div>Keep</div><SCRIPT>Remove</SCRIPT><Script>Remove</Script><p>Keep</p>';
|
|
49
|
+
const result = removeTag(html, 'script');
|
|
50
|
+
|
|
51
|
+
assert.strictEqual(result, '<div>Keep</div><p>Keep</p>');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should handle nested content', () => {
|
|
55
|
+
const html = '<div>Keep</div><script><div>Nested</div></script><p>Keep</p>';
|
|
56
|
+
const result = removeTag(html, 'script');
|
|
57
|
+
|
|
58
|
+
assert.strictEqual(result, '<div>Keep</div><p>Keep</p>');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// <!--section:code-->```js
|
|
2
|
+
/**
|
|
3
|
+
* Extract a named section from content marked with HTML comments
|
|
4
|
+
*
|
|
5
|
+
* @param {string} content - The content to process
|
|
6
|
+
* @param {string} sectionName - The section name(s) to extract
|
|
7
|
+
* @returns {string} The extracted section content
|
|
8
|
+
*/
|
|
9
|
+
export function section(content, sectionName) {
|
|
10
|
+
if (!content || typeof content !== "string") {
|
|
11
|
+
return content;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (typeof sectionName !== "string" || !sectionName) {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Normalize section name for comparison (trim whitespace)
|
|
19
|
+
const targetName = sectionName.trim().toLowerCase();
|
|
20
|
+
|
|
21
|
+
// Regex to match section markers with content up to the next section or end of string
|
|
22
|
+
// Captures: (1) section names, (2) content until next section marker or end
|
|
23
|
+
const sectionRegex = /<[!]--section:([^>]+)-->([\s\S]*?)(?=<[!]--section|$)/g;
|
|
24
|
+
|
|
25
|
+
let results = [];
|
|
26
|
+
let match;
|
|
27
|
+
|
|
28
|
+
// Find all sections
|
|
29
|
+
while ((match = sectionRegex.exec(content)) !== null) {
|
|
30
|
+
const namesStr = match[1];
|
|
31
|
+
const sectionContent = match[2];
|
|
32
|
+
const names = namesStr.split(",").map((n) => n.trim().toLowerCase());
|
|
33
|
+
|
|
34
|
+
// Check if any of the names match the target
|
|
35
|
+
if (names.includes(targetName)) {
|
|
36
|
+
results.push(sectionContent);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Join all matching sections
|
|
41
|
+
return results.join("");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* section filter - Extract a named section from content
|
|
46
|
+
*
|
|
47
|
+
* Usage in templates:
|
|
48
|
+
* {{ content | section('intro') }}
|
|
49
|
+
* {{ content | section('footer') }}
|
|
50
|
+
*
|
|
51
|
+
* Content format:
|
|
52
|
+
* <¡--section:intro-->
|
|
53
|
+
* This is the intro content
|
|
54
|
+
* <¡--section:main-->
|
|
55
|
+
* This is the main content
|
|
56
|
+
* <¡--section:footer,sidebar-->
|
|
57
|
+
* This appears in both footer and sidebar sections
|
|
58
|
+
*
|
|
59
|
+
* @param {Object} eleventyConfig - The Eleventy configuration object
|
|
60
|
+
*/
|
|
61
|
+
export function sectionFilter(eleventyConfig) {
|
|
62
|
+
eleventyConfig.addFilter("section", section);
|
|
63
|
+
}
|
|
64
|
+
/*```
|
|
65
|
+
|
|
66
|
+
<!--section:docs-->
|
|
67
|
+
### `section`
|
|
68
|
+
|
|
69
|
+
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.
|
|
70
|
+
|
|
71
|
+
**Usage:**
|
|
72
|
+
|
|
73
|
+
1. Mark sections in your content file (e.g., `post.md`):
|
|
74
|
+
|
|
75
|
+
⚠️ `NOTE:` The `¡` symbol is used instead of `!` only to give examples below. Use `!` in your actual content files.
|
|
76
|
+
|
|
77
|
+
```markdown
|
|
78
|
+
# My Post
|
|
79
|
+
|
|
80
|
+
<¡--section:intro-->
|
|
81
|
+
|
|
82
|
+
This is the introduction that appears at the top of the page.
|
|
83
|
+
|
|
84
|
+
<¡--section:main-->
|
|
85
|
+
|
|
86
|
+
This is the main body of the post with all the details.
|
|
87
|
+
|
|
88
|
+
<¡--section:summary,sidebar-->
|
|
89
|
+
|
|
90
|
+
This content appears in both the summary and the sidebar!
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
2. Use the filter in your templates: <!-- @TODO: better examples -->
|
|
94
|
+
|
|
95
|
+
```jinja2
|
|
96
|
+
{# Get the intro section #}
|
|
97
|
+
<div class="page-intro">
|
|
98
|
+
{{ content | section('intro') | safe }}
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{# Get the main section #}
|
|
102
|
+
<article>
|
|
103
|
+
{{ content | section('main') | safe }}
|
|
104
|
+
</article>
|
|
105
|
+
|
|
106
|
+
{# Get the sidebar section #}
|
|
107
|
+
<aside>
|
|
108
|
+
{{ content | section('sidebar') | safe }}
|
|
109
|
+
</aside>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Features:**
|
|
113
|
+
|
|
114
|
+
- **Multiple names**: A single section can have multiple names separated by commas: `<¡--section:name1,name2-->`
|
|
115
|
+
- **Case-insensitive**: Section names are matched without regard to case
|
|
116
|
+
- **Multiple occurrences**: If a section name appears multiple times, the filter concatenates all matching sections
|
|
117
|
+
- **Non-destructive**: Returns extracted content without modifying the original input
|
|
118
|
+
- **EOF support**: Sections continue until the next `<¡--section*-->` marker or the end of the file
|
|
119
|
+
|
|
120
|
+
**Syntax Rules:**
|
|
121
|
+
|
|
122
|
+
- Sections start with: `<¡--section:NAME-->` or `<¡--section:NAME1,NAME2-->`
|
|
123
|
+
- Sections end at the next `<¡--section*-->` marker or end of file
|
|
124
|
+
- Whitespace around names and inside comments is automatically trimmed
|
|
125
|
+
<!--section--> */
|