Class: Banzai::Filter::References::AbstractReferenceFilter
- Inherits:
-
ReferenceFilter
- Object
- HTML::Pipeline::Filter
- ReferenceFilter
- Banzai::Filter::References::AbstractReferenceFilter
- Includes:
- CrossProjectReference
- 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
Constant Summary collapse
- REFERENCE_PLACEHOLDER =
REFERENCE_PLACEHOLDER is used for re-escaping HTML text except found reference (which we replace with placeholder during re-scaping). The random number helps ensure it’s pretty close to unique. Since it’s a transitory value (it never gets saved) we can initialize once, and it doesn’t matter if it changes on a restart.
"_reference_#{SecureRandom.hex(16)}_"
- REFERENCE_PLACEHOLDER_PATTERN =
%r{#{REFERENCE_PLACEHOLDER}(\d+)}
Constants inherited from ReferenceFilter
ReferenceFilter::REFERENCE_TYPE_DATA_ATTRIBUTE
Instance Method Summary collapse
- #call ⇒ Object
- #data_attributes_for(text, parent, object, link_content: false, link_reference: false) ⇒ Object
-
#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_filter(text, pattern, link_content: 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_text(object, matches) ⇒ Object
- #object_link_text_extras(object, matches) ⇒ Object
- #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
Public: Find references in text (like ‘!123` for merge requests).
- #symbol_from_match(match) ⇒ 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 inherited from ReferenceFilter
call, #call_and_update_nodes, #each_node, #group, #nodes, #object_class, #project, #requires_unescaping?
Methods included from OutputSafety
Methods included from RequestStoreReferenceCache
#cached_call, #get_or_set_cache
Constructor Details
#initialize(doc, context = nil, result = nil) ⇒ AbstractReferenceFilter
Returns a new instance of AbstractReferenceFilter.
11 12 13 14 15 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 11 def initialize(doc, context = nil, result = nil) super @reference_cache = ReferenceCache.new(self, context, result) end |
Instance Method Details
#call ⇒ Object
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 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 118 def call return doc unless project || group || user reference_cache.load_reference_cache(nodes) if respond_to?(:parent_records) 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_text_when_pattern_matches(node, index, ref_pattern) do |content| object_link_filter(content, ref_pattern) end elsif element_node?(node) yield_valid_link(node) do |link, inner_html| if ref_pattern && link =~ ref_pattern_anchor replace_link_node_with_href(node, index, link) do object_link_filter(link, ref_pattern, link_content: inner_html) end next end next unless link_pattern if link == inner_html && inner_html =~ link_pattern_start replace_link_node_with_text(node, index) do object_link_filter(inner_html, link_pattern, link_reference: true) end next end if link =~ link_pattern_anchor replace_link_node_with_href(node, index, link) do object_link_filter(link, link_pattern, link_content: inner_html, link_reference: true) end next end end end end doc end |
#data_attributes_for(text, parent, object, link_content: false, link_reference: false) ⇒ Object
243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 243 def data_attributes_for(text, parent, object, link_content: false, link_reference: false) object_parent_type = parent.is_a?(Group) ? :group : :project { original: escape_html_entities(text), link: link_content, link_reference: link_reference, object_parent_type => parent.id, object_sym => object.id } end |
#find_object(parent_object, id) ⇒ Object
Implement in child class Example: project.merge_requests.find
78 79 80 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 78 def find_object(parent_object, id) raise NotImplementedError, "#{self.class} must implement method: #{__callee__}" end |
#find_object_cached(parent_object, id) ⇒ Object
94 95 96 97 98 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 94 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.
84 85 86 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 84 def find_object_from_link(parent_object, id) find_object(parent_object, id) end |
#find_object_from_link_cached(parent_object, id) ⇒ Object
100 101 102 103 104 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 100 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
106 107 108 109 110 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 106 def from_ref_cached(ref) cached_call("banzai_#{parent_type}_refs".to_sym, ref) do parent_from_ref(ref) end end |
#identifier(match_data) ⇒ Object
48 49 50 51 52 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 48 def identifier(match_data) symbol = symbol_from_match(match_data) parse_symbol(symbol, match_data) if object_class.reference_valid?(symbol) end |
#object_link_filter(text, pattern, link_content: 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 - Original content of the link being replaced. link_reference - True if this was using the link reference pattern,
false otherwise.
Returns a String with references replaced with links. All links have ‘gfm` and `gfm-OBJECT_NAME` class names attached for styling.
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 237 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 182 def object_link_filter(text, pattern, link_content: nil, link_reference: false) references_in(text, pattern) do |match, id, project_ref, namespace_ref, matches| parent_path = if parent_type == :group reference_cache.full_group_path(namespace_ref) else reference_cache.full_project_path(namespace_ref, project_ref) end parent = from_ref_cached(parent_path) if parent object = if link_reference find_object_from_link_cached(parent, id) else find_object_cached(parent, id) end end if object title = object_link_title(object, matches) klass = reference_class(object_sym) data_attributes = data_attributes_for( link_content || match, parent, object, link_content: !!link_content, 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 = link_content || object_link_text(object, matches) link = %(<a href="#{url}" #{data} title="#{escape_once(title)}" class="#{klass}">#{content}</a>) wrap_link(link, object) else match end end end |
#object_link_text(object, matches) ⇒ Object
273 274 275 276 277 278 279 280 281 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 273 def object_link_text(object, matches) parent = project || group || user text = object.reference_link_text(parent) extras = object_link_text_extras(object, matches) text += " (#{extras.join(", ")})" if extras.any? text end |
#object_link_text_extras(object, matches) ⇒ Object
255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 255 def object_link_text_extras(object, matches) extras = [] if matches.names.include?("anchor") && matches[:anchor] && matches[:anchor] =~ /\A\#note_(\d+)\z/ extras << "comment #{Regexp.last_match(1)}" end extension = matches[:extension] if matches.names.include?("extension") extras << extension if extension extras end |
#object_link_title(object, matches) ⇒ Object
269 270 271 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 269 def object_link_title(object, matches) object.title end |
#parent ⇒ Object
287 288 289 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 287 def parent parent_type == :project ? project : group end |
#parent_type ⇒ Object
283 284 285 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 283 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)`.
64 65 66 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 64 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)`.
72 73 74 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 72 def record_identifier(record) record.id end |
#references_in(text, pattern = object_class.reference_pattern) ⇒ Object
Public: Find references in text (like ‘!123` for merge requests)
references_in(text) do |match, id, project_ref, matches|
object = find_object(project_ref, id)
"<a href=...>#{object.to_reference}</a>"
end
text - String text to search.
Yields the String match, the Integer referenced object ID, an optional String of the external project reference, and all of the matchdata.
Returns a String replaced with the return of the block.
38 39 40 41 42 43 44 45 46 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 38 def references_in(text, pattern = object_class.reference_pattern) text.gsub(pattern) do |match| if ident = identifier($~) yield match, ident, $~[:project], $~[:namespace], $~ else match end end end |
#symbol_from_match(match) ⇒ Object
54 55 56 57 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 54 def symbol_from_match(match) key = object_sym match[key] if match.names.include?(key.to_s) end |
#url_for_object(object, parent_object) ⇒ Object
Implement in child class Example: project_merge_request_url
90 91 92 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 90 def url_for_object(object, parent_object) raise NotImplementedError, "#{self.class} must implement method: #{__callee__}" end |
#url_for_object_cached(object, parent_object) ⇒ Object
112 113 114 115 116 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 112 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
239 240 241 |
# File 'lib/banzai/filter/references/abstract_reference_filter.rb', line 239 def wrap_link(link, object) link end |