FIX: Generate unique HTML heading names (#12705)

Headings with the exact same name generated exactly the same heading
names, which was invalid. This replaces the old code for generating
names for non-English headings which were using URI encode and resulted
in unreadable headings.
This commit is contained in:
Bianca Nenciu 2021-04-16 10:54:19 +03:00 committed by GitHub
parent 42f6c9b6b9
commit 96a16123d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 14 additions and 20 deletions

View File

@ -670,7 +670,7 @@ eviltrout</p>
assert.cooked( assert.cooked(
"# #category-hashtag", "# #category-hashtag",
'<h1><a name="category-hashtag" class="anchor" href="#category-hashtag"></a><span class="hashtag">#category-hashtag</span></h1>', '<h1><a name="category-hashtag-1" class="anchor" href="#category-hashtag-1"></a><span class="hashtag">#category-hashtag</span></h1>',
"it works within ATX-style headers" "it works within ATX-style headers"
); );
@ -696,7 +696,7 @@ eviltrout</p>
test("Heading", function (assert) { test("Heading", function (assert) {
assert.cooked( assert.cooked(
"**Bold**\n----------", "**Bold**\n----------",
'<h2><a name="bold" class="anchor" href="#bold"></a><strong>Bold</strong></h2>', '<h2><a name="bold-1" class="anchor" href="#bold-1"></a><strong>Bold</strong></h2>',
"It will bold the heading" "It will bold the heading"
); );
}); });
@ -939,7 +939,7 @@ eviltrout</p>
assert.cooked( assert.cooked(
"## a\nb\n```\nc\n```", "## a\nb\n```\nc\n```",
'<h2><a name="a" class="anchor" href="#a"></a>a</h2>\n<p>b</p>\n<pre><code class="lang-auto">c\n</code></pre>', '<h2><a name="a-1" class="anchor" href="#a-1"></a>a</h2>\n<p>b</p>\n<pre><code class="lang-auto">c\n</code></pre>',
"it handles headings with code blocks after them." "it handles headings with code blocks after them."
); );
}); });

View File

@ -1,5 +1,3 @@
const SPECIAL_CHARACTERS_REGEX = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~’]/g;
export function setup(helper) { export function setup(helper) {
if (helper.getOptions().previewing) { if (helper.getOptions().previewing) {
return; return;
@ -7,7 +5,11 @@ export function setup(helper) {
helper.registerPlugin((md) => { helper.registerPlugin((md) => {
md.core.ruler.push("anchor", (state) => { md.core.ruler.push("anchor", (state) => {
for (let idx = 0, lvl = 0; idx < state.tokens.length; idx++) { for (
let idx = 0, lvl = 0, headingId = 0;
idx < state.tokens.length;
idx++
) {
if ( if (
state.tokens[idx].type === "blockquote_open" || state.tokens[idx].type === "blockquote_open" ||
(state.tokens[idx].type === "bbcode_open" && (state.tokens[idx].type === "bbcode_open" &&
@ -37,15 +39,7 @@ export function setup(helper) {
.replace(/^-+/, "") .replace(/^-+/, "")
.replace(/-+$/, ""); .replace(/-+$/, "");
if (slug.length === 0) { slug = `${slug || "heading"}-${++headingId}`;
slug = state.tokens[idx + 1].content
.replace(/\s+/g, "-")
.replace(SPECIAL_CHARACTERS_REGEX, "")
.replace(/\-\-+/g, "-")
.replace(/^-+/, "")
.replace(/-+$/, "");
slug = encodeURI(slug).replace(/%/g, "").substr(0, 24);
}
linkOpen.attrSet("name", slug); linkOpen.attrSet("name", slug);
linkOpen.attrSet("class", "anchor"); linkOpen.attrSet("class", "anchor");

View File

@ -189,8 +189,8 @@ describe PrettyText do
</div> </div>
HTML HTML
expect(cooked).to include("<h1>\n<a name=\"pre-heading\" class=\"anchor\" href=\"#pre-heading\"></a>Pre-heading</h1>") expect(cooked).to include("<h1>\n<a name=\"pre-heading-1\" class=\"anchor\" href=\"#pre-heading-1\"></a>Pre-heading</h1>")
expect(cooked).to include("<h1>\n<a name=\"post-heading\" class=\"anchor\" href=\"#post-heading\"></a>Post-heading</h1>") expect(cooked).to include("<h1>\n<a name=\"post-heading-2\" class=\"anchor\" href=\"#post-heading-2\"></a>Post-heading</h1>")
end end
it "does not break when there are headings before/after a poll without a title" do it "does not break when there are headings before/after a poll without a title" do
@ -211,7 +211,7 @@ describe PrettyText do
<div class="poll" data-poll-status="open" data-poll-name="poll"> <div class="poll" data-poll-status="open" data-poll-name="poll">
HTML HTML
expect(cooked).to include("<h1>\n<a name=\"pre-heading\" class=\"anchor\" href=\"#pre-heading\"></a>Pre-heading</h1>") expect(cooked).to include("<h1>\n<a name=\"pre-heading-1\" class=\"anchor\" href=\"#pre-heading-1\"></a>Pre-heading</h1>")
expect(cooked).to include("<h1>\n<a name=\"post-heading\" class=\"anchor\" href=\"#post-heading\"></a>Post-heading</h1>") expect(cooked).to include("<h1>\n<a name=\"post-heading-2\" class=\"anchor\" href=\"#post-heading-2\"></a>Post-heading</h1>")
end end
end end

View File

@ -1909,7 +1909,7 @@ HTML
html = <<~HTML html = <<~HTML
<h1> <h1>
<a name="hello-world" class="anchor" href="#hello-world"></a> <a name="hello-world-1" class="anchor" href="#hello-world-1"></a>
Hello world Hello world
</h1> </h1>
HTML HTML