FIX: Improve anchor links (#12683)
* FIX: Use theme color for anchor icon * FIX: Do not count anchor links * FIX: Do not count hashtags links either * DEV: Add tests for link_count * FIX: Disable anchors in quotes and preview * FIX: Try building some anchor slugs for unicode * DEV: Fix tests
This commit is contained in:
parent
eeaecd4fd2
commit
e4e2c7c66f
|
@ -664,7 +664,7 @@ eviltrout</p>
|
|||
|
||||
assert.cooked(
|
||||
"># #category-hashtag\n",
|
||||
'<blockquote>\n<h1><a name="category-hashtag" class="anchor" href="#category-hashtag"></a><span class="hashtag">#category-hashtag</span></h1>\n</blockquote>',
|
||||
'<blockquote>\n<h1><span class="hashtag">#category-hashtag</span></h1>\n</blockquote>',
|
||||
"it handles category hashtags in simple quotes"
|
||||
);
|
||||
|
||||
|
|
|
@ -1,15 +1,35 @@
|
|||
const SPECIAL_CHARACTERS_REGEX = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~’]/g;
|
||||
|
||||
export function setup(helper) {
|
||||
if (helper.getOptions().previewing) {
|
||||
return;
|
||||
}
|
||||
|
||||
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") {
|
||||
for (let idx = 0, lvl = 0; idx < state.tokens.length; idx++) {
|
||||
if (
|
||||
state.tokens[idx].type === "blockquote_open" ||
|
||||
(state.tokens[idx].type === "bbcode_open" &&
|
||||
state.tokens[idx].tag === "aside")
|
||||
) {
|
||||
++lvl;
|
||||
} else if (
|
||||
state.tokens[idx].type === "blockquote_close" ||
|
||||
(state.tokens[idx].type === "bbcode_close" &&
|
||||
state.tokens[idx].tag === "aside")
|
||||
) {
|
||||
--lvl;
|
||||
}
|
||||
|
||||
if (lvl > 0 || 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
|
||||
let slug = state.tokens[idx + 1].content
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/[^\w\-]+/g, "")
|
||||
|
@ -17,6 +37,16 @@ export function setup(helper) {
|
|||
.replace(/^-+/, "")
|
||||
.replace(/-+$/, "");
|
||||
|
||||
if (slug.length === 0) {
|
||||
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("class", "anchor");
|
||||
linkOpen.attrSet("href", "#" + slug);
|
||||
|
|
|
@ -99,7 +99,7 @@ $quote-share-maxwidth: 150px;
|
|||
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>'
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 512 512" fill="#{$primary-medium}"><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;
|
||||
|
|
|
@ -122,6 +122,10 @@ class PostAnalyzer
|
|||
cooked_stripped.css("a").each do |l|
|
||||
# Don't include @mentions in the link count
|
||||
next if link_is_a_mention?(l)
|
||||
# Don't include heading anchor in the link count
|
||||
next if link_is_an_anchor?(l)
|
||||
# Don't include hashtags in the link count
|
||||
next if link_is_a_hashtag?(l)
|
||||
@raw_links << l['href'].to_s
|
||||
end
|
||||
|
||||
|
@ -144,10 +148,17 @@ class PostAnalyzer
|
|||
private
|
||||
|
||||
def link_is_a_mention?(l)
|
||||
html_class = l['class']
|
||||
return false if html_class.blank?
|
||||
href = l['href'].to_s
|
||||
html_class.to_s['mention'] && href[/^\/u\//] || href[/^\/users\//]
|
||||
l['class'].to_s['mention'] && (href.start_with?("#{Discourse.base_path}/u/") || href.start_with?("#{Discourse.base_path}/users/"))
|
||||
end
|
||||
|
||||
def link_is_an_anchor?(l)
|
||||
l['class'].to_s['anchor'] && l['href'].to_s.start_with?('#')
|
||||
end
|
||||
|
||||
def link_is_a_hashtag?(l)
|
||||
href = l['href'].to_s
|
||||
l['class'].to_s['hashtag'] && (href.start_with?("#{Discourse.base_path}/c/") || href.start_with?("#{Discourse.base_path}/tag/"))
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -153,6 +153,10 @@ class Tag < ActiveRecord::Base
|
|||
SiteSetting.tagging_enabled
|
||||
end
|
||||
|
||||
def url
|
||||
"#{Discourse.base_path}/tag/#{UrlHelper.encode_component(self.name)}"
|
||||
end
|
||||
|
||||
def full_url
|
||||
"#{Discourse.base_url}/tag/#{UrlHelper.encode_component(self.name)}"
|
||||
end
|
||||
|
|
|
@ -110,7 +110,7 @@ module PrettyText
|
|||
[category.url, text]
|
||||
elsif (!is_tag && tag = Tag.find_by(name: text)) ||
|
||||
(is_tag && tag = Tag.find_by(name: text.gsub!(TAG_HASHTAG_POSTFIX, '')))
|
||||
["#{Discourse.base_url}/tag/#{tag.name}", text]
|
||||
[tag.url, text]
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -1226,7 +1226,7 @@ describe PrettyText do
|
|||
[
|
||||
"<span class=\"hashtag\">#unknown::tag</span>",
|
||||
"<a class=\"hashtag\" href=\"#{category2.url}\">#<span>known</span></a>",
|
||||
"<a class=\"hashtag\" href=\"http://test.localhost/tag/known\">#<span>known</span></a>",
|
||||
"<a class=\"hashtag\" href=\"/tag/known\">#<span>known</span></a>",
|
||||
"<a class=\"hashtag\" href=\"#{category.url}\">#<span>testing</span></a>"
|
||||
].each do |element|
|
||||
|
||||
|
@ -1247,7 +1247,7 @@ describe PrettyText do
|
|||
|
||||
cooked = PrettyText.cook("<A href='/a'>test</A> #known::tag")
|
||||
html = <<~HTML
|
||||
<p><a href="/a">test</a> <a class="hashtag" href="http://test.localhost/tag/known">#<span>known</span></a></p>
|
||||
<p><a href="/a">test</a> <a class="hashtag" href="/tag/known">#<span>known</span></a></p>
|
||||
HTML
|
||||
|
||||
expect(cooked).to eq(html.strip)
|
||||
|
|
|
@ -174,6 +174,8 @@ describe PostAnalyzer do
|
|||
let(:raw_post_one_link_md) { "[sherlock](http://www.bbc.co.uk/programmes/b018ttws)" }
|
||||
let(:raw_post_two_links_html) { "<a href='http://discourse.org'>discourse</a> <a href='http://twitter.com'>twitter</a>" }
|
||||
let(:raw_post_with_mentions) { "hello @novemberkilo how are you doing?" }
|
||||
let(:raw_post_with_anchors) { "# hello world" }
|
||||
let(:raw_post_with_hashtags) { "a category #{Fabricate(:category).slug} and a tag #{Fabricate(:tag).name}" }
|
||||
|
||||
it "returns 0 links for an empty post" do
|
||||
post_analyzer = PostAnalyzer.new("Hello world", nil)
|
||||
|
@ -185,6 +187,17 @@ describe PostAnalyzer do
|
|||
expect(post_analyzer.link_count).to eq(0)
|
||||
end
|
||||
|
||||
it "returns 0 links for a post with anchors" do
|
||||
post_analyzer = PostAnalyzer.new(raw_post_with_anchors, default_topic_id)
|
||||
expect(post_analyzer.link_count).to eq(0)
|
||||
end
|
||||
|
||||
it "returns 0 links for a post with mentions" do
|
||||
SiteSetting.tagging_enabled = true
|
||||
post_analyzer = PostAnalyzer.new(raw_post_with_hashtags, default_topic_id)
|
||||
expect(post_analyzer.link_count).to eq(0)
|
||||
end
|
||||
|
||||
it "returns links with href=''" do
|
||||
post_analyzer = PostAnalyzer.new('<a href="">Hello world</a>', nil)
|
||||
expect(post_analyzer.link_count).to eq(1)
|
||||
|
|
Loading…
Reference in New Issue