diff options
author | gkats <giannis.katsanos@gmail.com> | 2016-11-06 16:29:38 +0200 |
---|---|---|
committer | Sean McGivern <sean@gitlab.com> | 2016-12-06 15:14:30 +0000 |
commit | ed7d73d262e53fe9e0709fcd613a4d28e0ca4413 (patch) | |
tree | 45b59b2ca045e727c188c56bcc73d1f7d2284b65 | |
parent | 90938e4b1745692e543df4f3defa596ffb33d441 (diff) | |
download | gitlab-ce-gkats/gitlab-ce-23177-support-plus-sign-for-referable-title.tar.gz |
Support rich references in linksgkats/gitlab-ce-23177-support-plus-sign-for-referable-title
Currently supports up to 1 rich reference verbosity level. Level 1
includes the referable title in the link text. The rich reference suffix
is '+'. More than 3 '+' characters are ingored.
So, `#123+` becomes a link with text "123 The issue title".
- Modified issue regex to account for rich references verbosity
- Modified IssueReferenceFilter to add a data-rich-ref-verbosity attribute
- Added a post processing pipeline filter (RichReferenceFilter) to deal with
rich references
-rw-r--r-- | app/models/issue.rb | 4 | ||||
-rw-r--r-- | changelogs/unreleased/23177-support-plus-sign-for-referable-title.yml | 4 | ||||
-rw-r--r-- | lib/banzai/filter/abstract_reference_filter.rb | 25 | ||||
-rw-r--r-- | lib/banzai/filter/rich_reference_filter.rb | 39 | ||||
-rw-r--r-- | lib/banzai/pipeline/post_process_pipeline.rb | 3 | ||||
-rw-r--r-- | lib/banzai/pipeline/relative_link_pipeline.rb | 3 | ||||
-rw-r--r-- | spec/lib/banzai/filter/issue_reference_filter_spec.rb | 35 | ||||
-rw-r--r-- | spec/lib/banzai/filter/rich_reference_filter_spec.rb | 131 |
8 files changed, 233 insertions, 11 deletions
diff --git a/app/models/issue.rb b/app/models/issue.rb index fbf07040301..61399eb7dd5 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -121,11 +121,13 @@ class Issue < ActiveRecord::Base # Pattern used to extract `#123` issue references from text # - # This pattern supports cross-project references. + # This pattern supports cross-project references. Takes rich reference + # suffixes (+) into consideration, so it matches `#123+++`. def self.reference_pattern @reference_pattern ||= %r{ (#{Project.reference_pattern})? #{Regexp.escape(reference_prefix)}(?<issue>\d+) + (?<rich_ref_verbosity>#{Regexp.escape(Banzai::Filter::RichReferenceFilter::VERBOSITY_CHAR)}{1,3})? }x end diff --git a/changelogs/unreleased/23177-support-plus-sign-for-referable-title.yml b/changelogs/unreleased/23177-support-plus-sign-for-referable-title.yml new file mode 100644 index 00000000000..ebfee0d92b5 --- /dev/null +++ b/changelogs/unreleased/23177-support-plus-sign-for-referable-title.yml @@ -0,0 +1,4 @@ +--- +title: "Support rich references in links" +merge_request: 7313 +author: Giannis Katsanos diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 3740d4fb4cd..512b586650f 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -152,8 +152,6 @@ module Banzai title = object_link_title(object) klass = reference_class(object_sym) - data = data_attributes_for(link_content || match, project, object) - if matches.names.include?("url") && matches[:url] url = matches[:url] else @@ -161,8 +159,11 @@ module Banzai end content = link_content || object_link_text(object, matches) + rich_ref_verbosity = rich_ref_verbosity_for(matches) + data = data_attributes_for(link_content || match, project, object, rich_ref_verbosity) + - %(<a href="#{url}" #{data} + %(<a href="#{url}" #{data} #{} title="#{escape_once(title)}" class="#{klass}">#{content}</a>) else @@ -171,12 +172,20 @@ module Banzai end end - def data_attributes_for(text, project, object) - data_attribute( - original: text, - project: project.id, + def data_attributes_for(text, project, object, rich_ref_verbosity) + attributes = { + original: text, + project: project.id, + rich_ref_verbosity: rich_ref_verbosity, object_sym => object.id - ) + } + + data_attribute(attributes.compact) + end + + def rich_ref_verbosity_for(matches) + rich_ref_verbosity = matches.names.include?('rich_ref_verbosity') && matches[:rich_ref_verbosity] + { rich_ref_verbosity: rich_ref_verbosity.length } unless rich_ref_verbosity.blank? end def object_link_text_extras(object, matches) diff --git a/lib/banzai/filter/rich_reference_filter.rb b/lib/banzai/filter/rich_reference_filter.rb new file mode 100644 index 00000000000..95ef8810017 --- /dev/null +++ b/lib/banzai/filter/rich_reference_filter.rb @@ -0,0 +1,39 @@ +module Banzai + module Filter + # HTML filter to support rich references in links. + # + # Appends information on the link text depending on the verbosity + # level. It is expected to run in a post-processing pipeline. + class RichReferenceFilter < HTML::Pipeline::Filter + VERBOSITY_CHAR = '+' + + def call + links.each do |link| + handle_reference_verbosity_for(link) + end + doc + end + + private + + def links + @links ||= doc.css('a[data-rich-ref-verbosity]') + end + + def handle_reference_verbosity_for(link) + verbosity = link.attr('data-rich-ref-verbosity').to_i + if verbosity > 0 + link.content = "#{link.text} #{link.attr('title')}" + end + + if verbosity > 1 + # We support up to 3 levels of verbosity, but for now we implement + # only 1. Restore the missing '+' characters. + # This will not be needed if we support verbosity up to 3. + missing = VERBOSITY_CHAR * (verbosity - 1) + link.add_next_sibling(missing) + end + end + end + end +end diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb index ecff094b1e5..603cdcf5611 100644 --- a/lib/banzai/pipeline/post_process_pipeline.rb +++ b/lib/banzai/pipeline/post_process_pipeline.rb @@ -4,7 +4,8 @@ module Banzai def self.filters FilterArray[ Filter::RelativeLinkFilter, - Filter::RedactorFilter + Filter::RedactorFilter, + Filter::RichReferenceFilter ] end diff --git a/lib/banzai/pipeline/relative_link_pipeline.rb b/lib/banzai/pipeline/relative_link_pipeline.rb index 270990e7ab4..cde6901d653 100644 --- a/lib/banzai/pipeline/relative_link_pipeline.rb +++ b/lib/banzai/pipeline/relative_link_pipeline.rb @@ -3,7 +3,8 @@ module Banzai class RelativeLinkPipeline < BasePipeline def self.filters FilterArray[ - Filter::RelativeLinkFilter + Filter::RelativeLinkFilter, + Filter::RichReferenceFilter ] end end diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index 8f0b2db3e8e..c3c9f8462cf 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -114,6 +114,41 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do expect(link).to eq(href) end + + it 'processes references with "+" (rich reference verbosity) suffix' do + doc = reference_filter("See #{reference}+") + expect(doc.css('a').first.attr('href')). + to eq helper.url_for_issue(issue.iid, project) + end + + it 'strips the "+" rich reference verbosity suffix from the link text' do + doc = reference_filter("See #{reference}+") + expect(doc.text).not_to include('+') + end + + it 'adds a data-rich-ref-verbosity attribute for "+" suffix' do + doc = reference_filter("See #{reference}+") + link = doc.css('a').first + expect(link.attr('data-rich-ref-verbosity')).to eq('1') + end + + it 'supports up to 3 "+" characters for rich reference verbosity' do + doc = reference_filter("See #{reference}+++") + link = doc.css('a').first + expect(link.attr('data-rich-ref-verbosity')).to eq('3') + expect(doc.text).not_to include('+') + + doc = reference_filter("See #{reference}++++") + link = doc.css('a').first + expect(link.attr('data-rich-ref-verbosity')).to eq('3') + expect(doc.text).to match(/\+{1}/) + end + + it 'does not add a data-rich-ref-verbosity attribute if there\'s no "+" suffix' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + expect(link.to_html).not_to include('data-rich-ref-verbosity') + end end context 'cross-project reference' do diff --git a/spec/lib/banzai/filter/rich_reference_filter_spec.rb b/spec/lib/banzai/filter/rich_reference_filter_spec.rb new file mode 100644 index 00000000000..25281620f10 --- /dev/null +++ b/spec/lib/banzai/filter/rich_reference_filter_spec.rb @@ -0,0 +1,131 @@ +require 'spec_helper' + +describe Banzai::Filter::RichReferenceFilter, lib: true do + include FilterSpecHelper + + describe '#call' do + context 'with data-rich-ref-verbosity of 1' do + def doc(title) + Nokogiri::HTML.fragment( + "<a href='#' data-rich-ref-verbosity='1' title='#{title}'>#1</a>" + ) + end + + it 'appends the issue title' do + title = 'An issue title' + + expect(filter(doc(title)).at_css('a').text).to include(title) + end + + context 'but no title' do + it 'does not modify the document' do + document = doc(nil) + + expect(filter(document)).to eq(document) + end + end + end + + context 'with data-rich-ref-verbosity of 0' do + it 'does not modify the document' do + doc = Nokogiri::HTML.fragment("<a href='#' data-rich-ref-verbosity='0'>#1</a>") + + expect(filter(doc)).to eq(doc) + end + end + + context 'with no data-rich-ref-verbosity' do + it 'does not modify the document' do + doc = Nokogiri::HTML.fragment("<a href='#'>#1+</a>") + + expect(filter(doc)).to eq(doc) + end + end + + context 'with no <a> tag in the document' do + it 'does not modify the document' do + doc = Nokogiri::HTML.fragment('<p>Some text</p>') + + expect(filter(doc)).to eq(doc) + end + end + + context 'with data-rich-ref-verbosity of 2' do + let(:doc) do + Nokogiri::HTML.fragment("<a href='#' data-rich-ref-verbosity='2' title='#{title}'>#1</a>") + end + + let(:title) { 'An issue title' } + + it 'appends the issue title' do + expect(filter(doc).at_css('a').text).to include(title) + end + + it 'adds an extra "+"' do + expect(filter(doc).to_html).to include('+') + end + end + + context 'with data-rich-ref-verbosity of 3' do + let(:doc) do + Nokogiri::HTML.fragment("<a href='#' data-rich-ref-verbosity='3' title='#{title}'>#1</a>") + end + + let(:title) { 'An issue title' } + + it 'appends the issue title' do + expect(filter(doc).at_css('a').text).to include(title) + end + + it 'adds two extra "+"' do + expect(filter(doc).to_html).to include('++') + end + end + + context 'with more than one links with data-rich-ref-verbosity' do + let(:doc) do + Nokogiri::HTML.fragment( + "<a href='#' title='first title'>#1</a> but also " + + "<a href='#' data-rich-ref-verbosity='1' title='second title'>#2</a> and " + + "<a href='#' data-rich-ref-verbosity='1' title='third title'>#3</a>" + ) + end + + it 'appends the issue title for all links with data-rich-ref-verbosity' do + html = filter(doc).to_html + + expect(html).not_to include('#1 first title') + expect(html).to include('#2 second title') + expect(html).to include('#3 third title') + end + end + + context 'with text attribute following a rich-ref link and rich-ref-verbosity > 1' do + let(:doc) do + Nokogiri::HTML.fragment( + "<a href='#' data-rich-ref-verbosity='#{rich_ref_verbosity}' title='a title'>#1</a>Something else here." + ) + end + + let(:rich_ref_verbosity) { 2 } + + it 'adds an extra node with missing "+"s' do + expect(filter(doc).to_html).to include('+' * (rich_ref_verbosity - 1)) + end + end + + context 'with non-text attribute following a rich-ref link and rich-ref-verbosity > 1' do + let(:doc) do + Nokogiri::HTML.fragment( + "<a href='#' data-rich-ref-verbosity='2' title='a title'>#1</a><a href='#'>#2</a>" + ) + end + + it 'adds a text node for extra "+"s' do + text = filter(doc) + + expect(text.to_html).to include('</a>+<a href="#">#2</a>') + end + end + end +end |