diff --git a/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js b/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js index 5d8bd8ff49b..0d42988e1de 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js @@ -664,7 +664,7 @@ eviltrout

assert.cooked( "># #category-hashtag\n", - '
\n

#category-hashtag

\n
', + '
\n

#category-hashtag

\n
', "it handles category hashtags in simple quotes" ); diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/anchor.js b/app/assets/javascripts/pretty-text/engines/discourse-markdown/anchor.js index 37924d263f8..03941e6a0f8 100644 --- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/anchor.js +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/anchor.js @@ -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); diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index 0bd40e0f582..110d737bfa4 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -99,7 +99,7 @@ $quote-share-maxwidth: 150px; a.anchor { &:before { content: svg-uri( - '' + '' ); float: left; margin-left: -20px; diff --git a/app/models/post_analyzer.rb b/app/models/post_analyzer.rb index e56d1f7c494..bd8d58e79b0 100644 --- a/app/models/post_analyzer.rb +++ b/app/models/post_analyzer.rb @@ -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 diff --git a/app/models/tag.rb b/app/models/tag.rb index 78b30fb48ce..44c23734a05 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -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 diff --git a/lib/pretty_text/helpers.rb b/lib/pretty_text/helpers.rb index 62f3f63e8d0..b675900b4c9 100644 --- a/lib/pretty_text/helpers.rb +++ b/lib/pretty_text/helpers.rb @@ -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 diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index b241870c2cc..6cd371a64b9 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -1226,7 +1226,7 @@ describe PrettyText do [ "#unknown::tag", "#known", - "#known", + "#known", "#testing" ].each do |element| @@ -1247,7 +1247,7 @@ describe PrettyText do cooked = PrettyText.cook("test #known::tag") html = <<~HTML -

test #known

+

test #known

HTML expect(cooked).to eq(html.strip) diff --git a/spec/models/post_analyzer_spec.rb b/spec/models/post_analyzer_spec.rb index f7de1074ee5..8fad7b0cae6 100644 --- a/spec/models/post_analyzer_spec.rb +++ b/spec/models/post_analyzer_spec.rb @@ -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) { "discourse twitter" } 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('Hello world', nil) expect(post_analyzer.link_count).to eq(1)