summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgkats <giannis.katsanos@gmail.com>2016-11-06 16:29:38 +0200
committerSean McGivern <sean@gitlab.com>2016-12-06 15:14:30 +0000
commited7d73d262e53fe9e0709fcd613a4d28e0ca4413 (patch)
tree45b59b2ca045e727c188c56bcc73d1f7d2284b65
parent90938e4b1745692e543df4f3defa596ffb33d441 (diff)
downloadgitlab-ce-gkats/gitlab-ce-23177-support-plus-sign-for-referable-title.tar.gz
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.rb4
-rw-r--r--changelogs/unreleased/23177-support-plus-sign-for-referable-title.yml4
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb25
-rw-r--r--lib/banzai/filter/rich_reference_filter.rb39
-rw-r--r--lib/banzai/pipeline/post_process_pipeline.rb3
-rw-r--r--lib/banzai/pipeline/relative_link_pipeline.rb3
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb35
-rw-r--r--spec/lib/banzai/filter/rich_reference_filter_spec.rb131
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