summaryrefslogtreecommitdiff
path: root/node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js
blob: a55a13e216582d190b3f14bd06dfa266593c13bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import matchHelper from "posthtml-match-helper";
import { decodeHTML } from "entities";

import slugifyFilter from "../Filters/Slugify.js";
import MemoizeUtil from "../Util/MemoizeFunction.js";

const POSTHTML_PLUGIN_NAME = "11ty/eleventy/id-attribute";

function getTextNodeContent(node) {
	if (node.attrs?.["eleventy:id-ignore"] === "") {
		delete node.attrs["eleventy:id-ignore"];
		return "";
	}
	if (!node.content) {
		return "";
	}

	return node.content
		.map((entry) => {
			if (typeof entry === "string") {
				return entry;
			}
			if (Array.isArray(entry.content)) {
				return getTextNodeContent(entry);
			}
			return "";
		})
		.join("");
}

function IdAttributePlugin(eleventyConfig, options = {}) {
	if (!options.slugify) {
		options.slugify = MemoizeUtil(slugifyFilter);
	}
	if (!options.selector) {
		options.selector = "[id],h1,h2,h3,h4,h5,h6";
	}
	options.decodeEntities = options.decodeEntities ?? true;
	options.checkDuplicates = options.checkDuplicates ?? "error";

	eleventyConfig.htmlTransformer.addPosthtmlPlugin(
		"html",
		function idAttributePosthtmlPlugin(pluginOptions = {}) {
			if (typeof options.filter === "function") {
				if (options.filter(pluginOptions) === false) {
					return function () {};
				}
			}

			return function (tree) {
				// One per page
				let conflictCheck = {};
				// Cache heading nodes for conflict resolution
				let headingNodes = {};

				tree.match(matchHelper(options.selector), function (node) {
					if (node.attrs?.id) {
						let id = node.attrs?.id;
						if (conflictCheck[id]) {
							conflictCheck[id]++;
							if (headingNodes[id]) {
								// Rename conflicting assigned heading id
								let newId = `${id}-${conflictCheck[id]}`;
								headingNodes[newId] = headingNodes[id];
								headingNodes[newId].attrs.id = newId;
								delete headingNodes[id];
							} else if (options.checkDuplicates === "error") {
								// Existing `id` conflicts with assigned heading id, throw error
								throw new Error(
									'You have more than one HTML `id` attribute using the same value (id="' +
										id +
										'") in your template (' +
										pluginOptions.page.inputPath +
										"). You can disable this error in the IdAttribute plugin with the `checkDuplicates: false` option.",
								);
							}
						} else {
							conflictCheck[id] = 1;
						}
					} else if (!node.attrs?.id && node.content) {
						node.attrs = node.attrs || {};
						let textContent = getTextNodeContent(node);
						if (options.decodeEntities) {
							textContent = decodeHTML(textContent);
						}
						let id = options.slugify(textContent);

						if (conflictCheck[id]) {
							conflictCheck[id]++;
							id = `${id}-${conflictCheck[id]}`;
						} else {
							conflictCheck[id] = 1;
						}

						headingNodes[id] = node;
						node.attrs.id = id;
					}

					return node;
				});
			};
		},
		{
			// pluginOptions
			name: POSTHTML_PLUGIN_NAME,
		},
	);
}

export { IdAttributePlugin };