Skip to content

Commit

Permalink
FEATURE: Add anchor links to headings (#12379)
Browse files Browse the repository at this point in the history
  • Loading branch information
nbianca committed Mar 23, 2021
1 parent e48d055 commit 2ad9b3f
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 8 deletions.
Expand Up @@ -664,13 +664,13 @@ eviltrout</p>

assert.cooked(
"># #category-hashtag\n",
'<blockquote>\n<h1><span class="hashtag">#category-hashtag</span></h1>\n</blockquote>',
'<blockquote>\n<h1><a name="category-hashtag" class="anchor" href="#category-hashtag"></a><span class="hashtag">#category-hashtag</span></h1>\n</blockquote>',
"it handles category hashtags in simple quotes"
);

assert.cooked(
"# #category-hashtag",
'<h1><span class="hashtag">#category-hashtag</span></h1>',
'<h1><a name="category-hashtag" class="anchor" href="#category-hashtag"></a><span class="hashtag">#category-hashtag</span></h1>',
"it works within ATX-style headers"
);

Expand All @@ -696,7 +696,7 @@ eviltrout</p>
test("Heading", function (assert) {
assert.cooked(
"**Bold**\n----------",
"<h2><strong>Bold</strong></h2>",
'<h2><a name="bold" class="anchor" href="#bold"></a><strong>Bold</strong></h2>',
"It will bold the heading"
);
});
Expand Down Expand Up @@ -939,7 +939,7 @@ eviltrout</p>

assert.cooked(
"## a\nb\n```\nc\n```",
'<h2>a</h2>\n<p>b</p>\n<pre><code class="lang-auto">c\n</code></pre>',
'<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>',
"it handles headings with code blocks after them."
);
});
Expand Down
1 change: 1 addition & 0 deletions app/assets/javascripts/pretty-text/addon/allow-lister.js
Expand Up @@ -130,6 +130,7 @@ export default class AllowLister {
// Only add to `default` when you always want your allowlist to occur. In other words,
// don't change this for a plugin or a feature that can be disabled
export const DEFAULT_LIST = [
"a.anchor",
"a.attachment",
"a.hashtag",
"a.mention",
Expand Down
@@ -0,0 +1,29 @@
export function setup(helper) {
helper.registerPlugin((md) => {
md.core.ruler.push("anchor", (state) => {
for (let idx = 0; idx < state.tokens.length; idx++) {
if (state.tokens[idx].type !== "heading_open") {
continue;
}

const linkOpen = new state.Token("link_open", "a", 1);
const linkClose = new state.Token("link_close", "a", -1);

const slug = state.tokens[idx + 1].content
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^\w\-]+/g, "")
.replace(/\-\-+/g, "-")
.replace(/^-+/, "")
.replace(/-+$/, "");

linkOpen.attrSet("name", slug);
linkOpen.attrSet("class", "anchor");
linkOpen.attrSet("href", "#" + slug);

state.tokens[idx + 1].children.unshift(linkClose);
state.tokens[idx + 1].children.unshift(linkOpen);
}
});
});
}
14 changes: 14 additions & 0 deletions app/assets/stylesheets/common/base/topic-post.scss
Expand Up @@ -94,6 +94,20 @@ $quote-share-maxwidth: 150px;
h6 {
margin: 30px 0 10px;
line-height: $line-height-medium;

&:hover {
a.anchor {
&:before {
content: svg-uri(
'<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 512 512"><path d="M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z"></path></svg>'
);
float: left;
margin-left: -20px;
padding-right: 4px;
position: absolute;
}
}
}
}

h1 {
Expand Down
8 changes: 4 additions & 4 deletions plugins/poll/spec/lib/pretty_text_spec.rb
Expand Up @@ -189,8 +189,8 @@ def n(html)
</div>
HTML

expect(cooked).to include("<h1>Pre-heading</h1>")
expect(cooked).to include("<h1>Post-heading</h1>")
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=\"post-heading\" class=\"anchor\" href=\"#post-heading\"></a>Post-heading</h1>")
end

it "does not break when there are headings before/after a poll without a title" do
Expand All @@ -211,7 +211,7 @@ def n(html)
<div class="poll" data-poll-status="open" data-poll-name="poll">
HTML

expect(cooked).to include("<h1>Pre-heading</h1>")
expect(cooked).to include("<h1>Post-heading</h1>")
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=\"post-heading\" class=\"anchor\" href=\"#post-heading\"></a>Post-heading</h1>")
end
end
13 changes: 13 additions & 0 deletions spec/components/pretty_text_spec.rb
Expand Up @@ -1903,4 +1903,17 @@ def expect_cooked_match(raw, expected_cooked)
expect(cooked).to eq(html.strip)
end
end

it "adds anchor links to headings" do
cooked = PrettyText.cook('# Hello world')

html = <<~HTML
<h1>
<a name="hello-world" class="anchor" href="#hello-world"></a>
Hello world
</h1>
HTML

expect(cooked).to match_html(html)
end
end
1 change: 1 addition & 0 deletions spec/integrity/common_mark_spec.rb
Expand Up @@ -33,6 +33,7 @@
cooked.strip!
cooked.gsub!(" class=\"lang-auto\"", '')
cooked.gsub!(/<span class="hashtag">(.*)<\/span>/, "\\1")
cooked.gsub!(/<a name="(.*)" class="anchor" href="#\1*"><\/a>/, "")
# we don't care about this
cooked.gsub!("<blockquote>\n</blockquote>", "<blockquote></blockquote>")
html.gsub!("<blockquote>\n</blockquote>", "<blockquote></blockquote>")
Expand Down

1 comment on commit 2ad9b3f

@discoursebot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit has been mentioned on Discourse Meta. There might be relevant details there:

https://meta.discourse.org/t/using-to-make-title-bold-makes-it-a-link/184454/2

Please sign in to comment.