Skip to content

Commit

Permalink
Don’t rename any existing id attributes, do rename heading node ids…
Browse files Browse the repository at this point in the history
… out of order when conflicts arise #3430
  • Loading branch information
zachleat committed Sep 4, 2024
1 parent 48886e3 commit 8dbab69
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 12 deletions.
31 changes: 26 additions & 5 deletions src/Plugins/IdAttributePlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function IdAttributePlugin(eleventyConfig, options = {}) {
options.selector = "[id],h1,h2,h3,h4,h5,h6";
}
options.decodeEntities = options.decodeEntities ?? true;
options.checkDuplicates = options.checkDuplicates ?? "error";

eleventyConfig.htmlTransformer.addPosthtmlPlugin(
"html",
Expand All @@ -43,28 +44,48 @@ function IdAttributePlugin(eleventyConfig, options = {}) {
return function (tree) {
// One per page
let conflictCheck = {};
// Cache heading nodes for conflict resolution
let headingNodes = {};

tree.match(matchHelper(options.selector), function (node) {
let id;
if (node.attrs?.id) {
id = 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(
"Duplicate `id` attribute (" +
id +
") in markup on " +
pluginOptions.page.inputPath,
);
}
} 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);
}
id = options.slugify(textContent);
}
let id = options.slugify(textContent);

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

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

Expand Down
2 changes: 1 addition & 1 deletion src/Plugins/InputPathToUrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ function TransformPlugin(eleventyConfig, defaultOptions = {}) {

let suffix = "";
[suffix, targetFilepathOrUrl] = parseFilePath(targetFilepathOrUrl);
// @ts-ignore
targetFilepathOrUrl = normalizeInputPath(
targetFilepathOrUrl,
inputDir,
// @ts-ignore
this.page.inputPath,
contentMap,
);
Expand Down
53 changes: 47 additions & 6 deletions test/IdAttributePluginTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ test("Issue #3424, id attribute conflicts (id attribute supplied last)", async (
});

let results = await elev.toJSON();
t.is(results[0].content.trim(), `<h1 id="testing">Testing</h1><div id="testing-2"></div>`);
t.is(results[0].content.trim(), `<h1 id="testing-2">Testing</h1><div id="testing"></div>`);
});

test("Issue #3424, id attribute conflicts (hard coded id conflicts)", async (t) => {
Expand All @@ -65,7 +65,46 @@ test("Issue #3424, id attribute conflicts (hard coded id conflicts)", async (t)
});

let results = await elev.toJSON();
t.is(results[0].content.trim(), `<h1 id="testing">Testing</h1><h1 id="testing-2">Testing</h1>`);
t.is(results[0].content.trim(), `<h1 id="testing-2">Testing</h1><h1 id="testing">Testing</h1>`);
});

test("Issue #3424, id attribute conflicts (three deep, hard coded id conflicts)", async (t) => {
let elev = new Eleventy("./test/stubs-virtual/", "./test/stubs-virtual/_site", {
config: function (eleventyConfig) {
eleventyConfig.addPlugin(IdAttributePlugin);

eleventyConfig.addTemplate("test.html", `<h1>Testing</h1><h1>Testing</h1><h1 id="testing">Testing</h1>`, {});
},
});

let results = await elev.toJSON();
t.is(results[0].content.trim(), `<h1 id="testing-3">Testing</h1><h1 id="testing-2">Testing</h1><h1 id="testing">Testing</h1>`);
});

test("Issue #3424, id attribute conflicts (four deep, hard coded id conflicts)", async (t) => {
let elev = new Eleventy("./test/stubs-virtual/", "./test/stubs-virtual/_site", {
config: function (eleventyConfig) {
eleventyConfig.addPlugin(IdAttributePlugin);

eleventyConfig.addTemplate("test.html", `<h1>Testing</h1><h1>Testing</h1><h1>Testing</h1><h1 id="testing">Testing</h1>`, {});
},
});

let results = await elev.toJSON();
t.is(results[0].content.trim(), `<h1 id="testing-4">Testing</h1><h1 id="testing-2">Testing</h1><h1 id="testing-3">Testing</h1><h1 id="testing">Testing</h1>`);
});

test("Issue #3424, id attribute conflicts (five deep, hard coded id conflicts)", async (t) => {
let elev = new Eleventy("./test/stubs-virtual/", "./test/stubs-virtual/_site", {
config: function (eleventyConfig) {
eleventyConfig.addPlugin(IdAttributePlugin);

eleventyConfig.addTemplate("test.html", `<h1>Testing</h1><h1>Testing</h1><h1>Testing</h1><h1>Testing</h1><h1 id="testing">Testing</h1>`, {});
},
});

let results = await elev.toJSON();
t.is(results[0].content.trim(), `<h1 id="testing-5">Testing</h1><h1 id="testing-2">Testing</h1><h1 id="testing-3">Testing</h1><h1 id="testing-4">Testing</h1><h1 id="testing">Testing</h1>`);
});

test("Issue #3424, id attribute conflicts (two hard coded id conflicts)", async (t) => {
Expand All @@ -76,9 +115,10 @@ test("Issue #3424, id attribute conflicts (two hard coded id conflicts)", async
eleventyConfig.addTemplate("test.html", `<h1 id="testing">Testing</h1><h1 id="testing">Testing</h1>`, {});
},
});
elev.disableLogger();

let results = await elev.toJSON();
t.is(results[0].content.trim(), `<h1 id="testing">Testing</h1><h1 id="testing-2">Testing</h1>`);
let e = await t.throwsAsync(() => elev.toJSON());
t.is(e.originalError.originalError.toString(), "Error: Duplicate `id` attribute (testing) in markup on ./test/stubs-virtual/test.html");
});

test("Issue #3424, filter callback skips", async (t) => {
Expand All @@ -93,12 +133,13 @@ test("Issue #3424, filter callback skips", async (t) => {
}
});

eleventyConfig.addTemplate("test.html", `<h1 id="testing">Testing</h1><h1 id="testing">Testing</h1>`, {});
eleventyConfig.addTemplate("test.html", `<h1>Testing</h1><h1 id="testing">Testing</h1>`, {});
eleventyConfig.addTemplate("test-skipped.html", `<h1 id="testing">Testing</h1><h1 id="testing">Testing</h1>`, {});
},
});
elev.disableLogger();

let results = await elev.toJSON();
t.is(results[0].content.trim(), `<h1 id="testing">Testing</h1><h1 id="testing-2">Testing</h1>`);
t.is(results[0].content.trim(), `<h1 id="testing-2">Testing</h1><h1 id="testing">Testing</h1>`);
t.is(results[1].content.trim(), `<h1 id="testing">Testing</h1><h1 id="testing">Testing</h1>`);
});

0 comments on commit 8dbab69

Please sign in to comment.