Class: Mato::HtmlFilters::MentionLink

Inherits:
Object
  • Object
show all
Includes:
Concerns::HtmlNodeCheckable
Defined in:
lib/mato/html_filters/mention_link.rb

Constant Summary collapse

MENTION_PATTERN =

e.g. @foo

/\@[a-zA-Z0-9_\-]+\b/

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Concerns::HtmlNodeCheckable

has_ancestor?

Constructor Details

#initialize(pattern = MENTION_PATTERN, &link_builder) ⇒ MentionLink

Returns a new instance of MentionLink.

Parameters:

  • pattern (Regexp) (defaults to: MENTION_PATTERN)
  • link_builder (Proc)

    A block that takes Hash<String, Array<Nokogiri::XML::Node>>



20
21
22
23
# File 'lib/mato/html_filters/mention_link.rb', line 20

def initialize(pattern = MENTION_PATTERN, &link_builder)
  @pattern = pattern
  @link_builder = link_builder
end

Instance Attribute Details

Returns:

  • (Proc)


14
15
16
# File 'lib/mato/html_filters/mention_link.rb', line 14

def link_builder
  @link_builder
end

#patternRegexp (readonly)

Returns:

  • (Regexp)


11
12
13
# File 'lib/mato/html_filters/mention_link.rb', line 11

def pattern
  @pattern
end

Instance Method Details

#call(doc) ⇒ Object

Parameters:

  • doc (Nokogiri::HTML4::DocumentFragment)


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/mato/html_filters/mention_link.rb', line 26

def call(doc)
  candidate_map = {}
  candidates = []

  doc.xpath('.//text()').each do |text_node|
    next if has_ancestor?(text_node, 'a', 'code', 'pre')

    candidate_html = text_node.content.gsub(pattern) do |mention|
      "<span class='mention-candidate'>#{mention}</span>"
    end

    next if text_node.content == candidate_html

    candidate_fragment = text_node.replace(candidate_html)
    candidate_fragment.css('span.mention-candidate').each do |mention_element|
      next unless mention_element.child

      (candidate_map[mention_element.child.content] ||= []) << mention_element
    end

    candidates << candidate_fragment
  end

  unless candidate_map.empty?
    link_builder.call(candidate_map)

    # cleanup
    candidates.each do |candidate_fragment|
      candidate_fragment.css('span.mention-candidate').each do |node|
        next unless node.child
        # If link_builder calls Node#replace for a node,
        # the node's parent becames nil.
        # Node#replace doesn't accept node that doesn't have parent since Nokogiri v1.11.0,
        # so we need to skip it.
        next unless node.parent

        node.replace(node.child.content)
      end
    end
  end
end