Class: Banzai::Filter::References::AbstractReferenceFilter
- Inherits:
-
ReferenceFilter
- Object
- HTML::Pipeline::Filter
- ReferenceFilter
- Banzai::Filter::References::AbstractReferenceFilter
- Defined in:
- lib/banzai/filter/references/abstract_reference_filter.rb
Overview
Issues, merge requests, Snippets, Commits and Commit Ranges share similar functionality in reference filtering.
Direct Known Subclasses
CommitRangeReferenceFilter, CommitReferenceFilter, DesignReferenceFilter, IssuableReferenceFilter, LabelReferenceFilter, MilestoneReferenceFilter, SnippetReferenceFilter, WikiPageReferenceFilter
Constant Summary
Constants included from Concerns::TimeoutFilterHandler
Concerns::TimeoutFilterHandler::COMPLEX_MARKDOWN_MESSAGE, Concerns::TimeoutFilterHandler::RENDER_TIMEOUT, Concerns::TimeoutFilterHandler::SANITIZATION_RENDER_TIMEOUT
Constants included from Concerns::PipelineTimingCheck
Concerns::PipelineTimingCheck::MAX_PIPELINE_SECONDS
Constants inherited from ReferenceFilter
ReferenceFilter::REFERENCE_TYPE_ATTRIBUTE, ReferenceFilter::REFERENCE_TYPE_DATA_ATTRIBUTE_NAME
Constants included from Concerns::TextReplacer
Concerns::TextReplacer::REFERENCE_PLACEHOLDER, Concerns::TextReplacer::REFERENCE_PLACEHOLDER_PATTERN
Instance Method Summary collapse
- #call ⇒ Object
-
#data_attributes_for(original, parent, object, link_content: false, link_reference: false) ⇒ Object
“link_content” is true when “original” is the inner HTML content, or false when “original” is HTML-escaped plain text representing the link as written.
-
#find_object(parent_object, id) ⇒ Object
Implement in child class Example: project.merge_requests.find.
- #find_object_cached(parent_object, id) ⇒ Object
-
#find_object_from_link(parent_object, id) ⇒ Object
Override if the link reference pattern produces a different ID (global ID vs internal ID, for instance) to the regular reference pattern.
- #find_object_from_link_cached(parent_object, id) ⇒ Object
- #from_ref_cached(ref) ⇒ Object
- #identifier(match_data) ⇒ Object
-
#initialize(doc, context = nil, result = nil) ⇒ AbstractReferenceFilter
constructor
A new instance of AbstractReferenceFilter.
-
#object_link_content_html(object, matches) ⇒ Object
Provides the default content of the created link, if there was no original content to use.
-
#object_link_content_html_extras(object, matches) ⇒ Object
Provides extra content for the default content of the created link, used by the default implementation of #object_link_content_html.
-
#object_link_filter(text, pattern, link_content_html: nil, link_reference: false) ⇒ Object
Replace references (like ‘!123` for merge requests) in text with links to the referenced object’s details page.
- #object_link_title(object, matches) ⇒ Object
- #parent ⇒ Object
- #parent_type ⇒ Object
-
#parse_symbol(symbol, match_data) ⇒ Object
Transform a symbol extracted from the text to a meaningful value In most cases these will be integers, so we call #to_i by default.
-
#record_identifier(record) ⇒ Object
We assume that most classes are identifying records by ID.
-
#references_in(text, pattern = object_class.reference_pattern) ⇒ Object
See ReferenceFilter#references_in for requirements on the input, block, and return value of this function.
- #symbol_from_match_data(match_data) ⇒ Object
-
#url_for_object(object, parent_object) ⇒ Object
Implement in child class Example: project_merge_request_url.
- #url_for_object_cached(object, parent_object) ⇒ Object
- #wrap_link(link, object) ⇒ Object
Methods included from CrossProjectReference
Methods included from Concerns::PipelineTimingCheck
Methods inherited from ReferenceFilter
call, #call_and_update_nodes, #each_node, #group, #nodes, #nodes?, #object_class, #project, #requires_unescaping?
Methods included from Concerns::TextReplacer
#replace_references_in_text_with_html
Methods included from Concerns::HtmlWriter
Methods included from Concerns::OutputSafety
Methods included from RequestStoreReferenceCache
#cached_call, #get_or_set_cache
Constructor Details
#initialize(doc, context = nil, result = nil) ⇒ AbstractReferenceFilter
13 14 15 16 17 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 13 def initialize(doc, context = nil, result = nil) super @reference_cache = ReferenceCache.new(self, context, result) end |
Instance Method Details
#call ⇒ Object
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 105 def call return doc unless project || group || user reference_cache.load_reference_cache(nodes) if respond_to?(:parent_records) && nodes.present? ref_pattern = object_reference_pattern link_pattern = object_class.link_reference_pattern # Compile often used regexps only once outside of the loop ref_pattern_anchor = /\A#{ref_pattern}\z/ link_pattern_start = /\A#{link_pattern}/ link_pattern_anchor = /\A#{link_pattern}\z/ nodes.each_with_index do |node, index| if text_node?(node) && ref_pattern replace_node_when_text_matches(node, index, ref_pattern) do |content| object_link_filter(content, ref_pattern) end elsif element_node?(node) yield_valid_link(node) do |link, text, inner_html| if ref_pattern && link =~ ref_pattern_anchor html = object_link_filter(link, ref_pattern_anchor, link_content_html: inner_html) replace_node_with_html(node, index, html) if html next end next unless link_pattern if link == text && text =~ link_pattern_start html = object_link_filter(text, link_pattern_start, link_reference: true) replace_node_with_html(node, index, html) if html next end if link_pattern_anchor.match?(link) html = object_link_filter(link, link_pattern_anchor, link_content_html: inner_html, link_reference: true) replace_node_with_html(node, index, html) if html next end end end end doc end |
#data_attributes_for(original, parent, object, link_content: false, link_reference: false) ⇒ Object
“link_content” is true when “original” is the inner HTML content, or false when “original” is HTML-escaped plain text representing the link as written.
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 244 def data_attributes_for(original, parent, object, link_content: false, link_reference: false) parent_id = case parent when Group { group: parent.id, namespace: parent.id } when Project { project: parent.id } when Namespaces::ProjectNamespace { namespace: parent.id, project: parent.project.id } end { original: original, link: link_content, link_reference: link_reference, object_sym => object.id }.merge(parent_id) end |
#find_object(parent_object, id) ⇒ Object
Implement in child class Example: project.merge_requests.find
65 66 67 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 65 def find_object(parent_object, id) raise NotImplementedError, "#{self.class} must implement method: #{__callee__}" end |
#find_object_cached(parent_object, id) ⇒ Object
81 82 83 84 85 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 81 def find_object_cached(parent_object, id) cached_call(:banzai_find_object, id, path: [object_class, parent_object.id]) do find_object(parent_object, id) end end |
#find_object_from_link(parent_object, id) ⇒ Object
Override if the link reference pattern produces a different ID (global ID vs internal ID, for instance) to the regular reference pattern.
71 72 73 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 71 def find_object_from_link(parent_object, id) find_object(parent_object, id) end |
#find_object_from_link_cached(parent_object, id) ⇒ Object
87 88 89 90 91 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 87 def find_object_from_link_cached(parent_object, id) cached_call(:banzai_find_object_from_link, id, path: [object_class, parent_object.id]) do find_object_from_link(parent_object, id) end end |
#from_ref_cached(ref) ⇒ Object
93 94 95 96 97 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 93 def from_ref_cached(ref) cached_call(:"banzai_#{parent_type}_refs", ref) do parent_from_ref(ref) end end |
#identifier(match_data) ⇒ Object
35 36 37 38 39 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 35 def identifier(match_data) symbol = symbol_from_match_data(match_data) parse_symbol(symbol, match_data) if object_class.reference_valid?(symbol) end |
#object_link_content_html(object, matches) ⇒ Object
Provides the default content of the created link, if there was no original content to use.
Returns a HTML String. If you override this, ensure you return HTML by escaping any text.
265 266 267 268 269 270 271 272 273 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 265 def object_link_content_html(object, matches) parent = project || group || user html = CGI.escapeHTML(object.reference_link_text(parent)) extras = object_link_content_html_extras(object, matches) html += " (#{extras.join(', ')})" if extras.any? html end |
#object_link_content_html_extras(object, matches) ⇒ Object
Provides extra content for the default content of the created link, used by the default implementation of #object_link_content_html.
Returns an Array of HTML Strings. If you override this, ensure you return HTML by escaping any text.
279 280 281 282 283 284 285 286 287 288 289 290 291 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 279 def object_link_content_html_extras(object, matches) extras = [] if matches.names.include?("anchor") && matches[:anchor] && matches[:anchor] =~ /\A\#note_(\d+)\z/ extras << CGI.escapeHTML("comment #{Regexp.last_match(1)}") end extension = matches[:extension] if matches.names.include?("extension") extras << CGI.escapeHTML(extension) if extension extras end |
#object_link_filter(text, pattern, link_content_html: nil, link_reference: false) ⇒ Object
Replace references (like ‘!123` for merge requests) in text with links to the referenced object’s details page.
text - String text to replace references in. pattern - Reference pattern to match against. link_content_html - Original HTML content of the link being replaced. link_reference - True if this was using the link reference pattern,
false otherwise.
Returns String HTML with references replaced with links, or nil if no replacements were made. All links have ‘gfm` and `gfm-OBJECT_NAME` class names attached for styling.
Note carefully:
-
‘text` is text, not HTML.
-
‘link_content_html`, if provided, is HTML.
-
The return value is HTML (or nil).
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 174 def object_link_filter(text, pattern, link_content_html: nil, link_reference: false) references_in(text, pattern) do |match_text, id, project_ref, namespace_ref, matches| parent_path = if parent_type == :group reference_cache.full_group_path(namespace_ref) elsif parent_type == :namespace reference_cache.full_namespace_path(matches) else reference_cache.full_project_path(namespace_ref, project_ref, matches) end parent = from_ref_cached(parent_path) next unless parent object = if link_reference find_object_from_link_cached(parent, id) else find_object_cached(parent, id) end next unless object title = object_link_title(object, matches) klass = reference_class(object_sym) data_attributes = data_attributes_for( link_content_html || CGI.escapeHTML(match_text), parent, object, link_content: !!link_content_html, link_reference: link_reference ) data_attributes[:reference_format] = matches[:format] if matches.names.include?("format") data_attributes.merge!(additional_object_attributes(object)) data = data_attribute(data_attributes) url = if matches.names.include?("url") && matches[:url] matches[:url] else url_for_object_cached(object, parent) end url.chomp!(matches[:format]) if matches.names.include?("format") content = if context[:link_text] CGI.escapeHTML(context[:link_text]) else link_content_html || object_link_content_html(object, matches) end link = write_opening_tag("a", { "href" => url, "title" => title, "class" => klass, **data }) << content.to_s << "</a>" wrap_link(link, object) end end |
#object_link_title(object, matches) ⇒ Object
293 294 295 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 293 def object_link_title(object, matches) object.title end |
#parent ⇒ Object
301 302 303 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 301 def parent parent_type == :project ? project : group end |
#parent_type ⇒ Object
297 298 299 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 297 def parent_type :project end |
#parse_symbol(symbol, match_data) ⇒ Object
Transform a symbol extracted from the text to a meaningful value In most cases these will be integers, so we call #to_i by default
This method has the contract that if a string ‘ref` refers to a record `record`, then `parse_symbol(ref) == record_identifier(record)`.
51 52 53 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 51 def parse_symbol(symbol, match_data) symbol.to_i end |
#record_identifier(record) ⇒ Object
We assume that most classes are identifying records by ID.
This method has the contract that if a string ‘ref` refers to a record `record`, then `class.parse_symbol(ref) == record_identifier(record)`.
59 60 61 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 59 def record_identifier(record) record.id end |
#references_in(text, pattern = object_class.reference_pattern) ⇒ Object
See ReferenceFilter#references_in for requirements on the input, block, and return value of this function.
This function yields the String match, the Integer referenced object ID, an optional String of the external project reference, an optional String of the namespace reference, and the full MatchData.
25 26 27 28 29 30 31 32 33 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 25 def references_in(text, pattern = object_class.reference_pattern) replace_references_in_text_with_html(Gitlab::Utils::Gsub.gsub_with_limit(text, pattern, limit: Banzai::Filter::FILTER_ITEM_LIMIT)) do |match_data| if ident = identifier(match_data) yield match_data[0], ident, match_data.named_captures['project'], match_data.named_captures['namespace'], match_data end end end |
#symbol_from_match_data(match_data) ⇒ Object
41 42 43 44 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 41 def symbol_from_match_data(match_data) key = object_sym match_data[key] if match_data.names.include?(key.to_s) end |
#url_for_object(object, parent_object) ⇒ Object
Implement in child class Example: project_merge_request_url
77 78 79 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 77 def url_for_object(object, parent_object) raise NotImplementedError, "#{self.class} must implement method: #{__callee__}" end |
#url_for_object_cached(object, parent_object) ⇒ Object
99 100 101 102 103 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 99 def url_for_object_cached(object, parent_object) cached_call(:banzai_url_for_object, object, path: [object_class, parent_object.id]) do url_for_object(object, parent_object) end end |
#wrap_link(link, object) ⇒ Object
238 239 240 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 238 def wrap_link(link, object) link end |