reference_filter.rb 3.6 KB
Newer Older
1 2
module Banzai
  module Filter
3 4 5 6 7
    # Base class for GitLab Flavored Markdown reference filters.
    #
    # References within <pre>, <code>, <a>, and <style> elements are ignored.
    #
    # Context options:
8
    #   :project (required) - Current project, ignored if reference is cross-project.
9 10
    #   :only_path          - Generate path-only links.
    class ReferenceFilter < HTML::Pipeline::Filter
11 12
      class << self
        attr_accessor :reference_type
13 14
      end

15 16
      # Returns a data attribute String to attach to a reference link
      #
R
Robert Speicher 已提交
17 18
      # attributes - Hash, where the key becomes the data attribute name and the
      #              value is the data attribute value
19 20 21
      #
      # Examples:
      #
R
Robert Speicher 已提交
22
      #   data_attribute(project: 1, issue: 2)
23
      #   # => "data-reference-filter=\"SomeReferenceFilter\" data-project=\"1\" data-issue=\"2\""
R
Robert Speicher 已提交
24 25
      #
      #   data_attribute(project: 3, merge_request: 4)
26
      #   # => "data-reference-filter=\"SomeReferenceFilter\" data-project=\"3\" data-merge-request=\"4\""
27 28
      #
      # Returns a String
29
      def data_attribute(attributes = {})
30 31 32
        attributes = attributes.reject { |_, v| v.nil? }

        attributes[:reference_type] = self.class.reference_type
33
        attributes.delete(:original) if context[:no_original_data]
D
Douwe Maan 已提交
34
        attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{escape_once(value)}") }.join(" ")
35 36
      end

37
      def escape_once(html)
D
Douwe Maan 已提交
38
        html.html_safe? ? html : ERB::Util.html_escape_once(html)
39 40
      end

41 42
      def ignore_ancestor_query
        @ignore_ancestor_query ||= begin
D
Douwe Maan 已提交
43 44
          parents = %w(pre code a style)
          parents << 'blockquote' if context[:ignore_blockquotes]
45

46 47
          parents.map { |n| "ancestor::#{n}" }.join(' or ')
        end
48 49 50 51 52 53 54
      end

      def project
        context[:project]
      end

      def reference_class(type)
55
        "gfm gfm-#{type}"
56 57
      end

58
      # Ensure that a :project key exists in context
59
      #
60 61 62
      # Note that while the key might exist, its value could be nil!
      def validate
        needs :project
63 64
      end

65
      # Iterates over all <a> and text() nodes in a document.
D
Douwe Maan 已提交
66
      #
67 68 69 70 71 72 73 74
      # Nodes are skipped whenever their ancestor is one of the nodes returned
      # by `ignore_ancestor_query`. Link tags are not processed if they have a
      # "gfm" class or the "href" attribute is empty.
      def each_node
        query = %Q{descendant-or-self::text()[not(#{ignore_ancestor_query})]
        | descendant-or-self::a[
          not(contains(concat(" ", @class, " "), " gfm ")) and not(@href = "")
        ]}
75

76 77 78 79
        doc.xpath(query).each do |node|
          yield node
        end
      end
80

81 82 83 84
      # Yields the link's URL and text whenever the node is a valid <a> tag.
      def yield_valid_link(node)
        link = CGI.unescape(node.attr('href').to_s)
        text = node.text
85

86
        return unless link.force_encoding('UTF-8').valid_encoding?
87

88
        yield link, text
89 90
      end

91 92
      def replace_text_when_pattern_matches(node, pattern)
        return unless node.text =~ pattern
D
Douwe Maan 已提交
93

94 95
        content = node.to_html
        html = yield content
D
Douwe Maan 已提交
96

97 98
        node.replace(html) unless content == html
      end
D
Douwe Maan 已提交
99

100 101
      def replace_link_node_with_text(node, link)
        html = yield
D
Douwe Maan 已提交
102

103 104
        node.replace(html) unless html == node.text
      end
D
Douwe Maan 已提交
105

106 107
      def replace_link_node_with_href(node, link)
        html = yield
D
Douwe Maan 已提交
108

109 110
        node.replace(html) unless html == link
      end
D
Douwe Maan 已提交
111

112 113
      def text_node?(node)
        node.is_a?(Nokogiri::XML::Text)
D
Douwe Maan 已提交
114 115
      end

116 117
      def element_node?(node)
        node.is_a?(Nokogiri::XML::Element)
118 119 120 121
      end
    end
  end
end