File: %40mention_filter.rb

package info (click to toggle)
ruby-html-pipeline 2.14.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 424 kB
  • sloc: ruby: 2,265; sh: 13; makefile: 6
file content (140 lines) | stat: -rw-r--r-- 5,040 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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
# frozen_string_literal: true

require 'set'

module HTML
  class Pipeline
    # HTML filter that replaces @user mentions with links. Mentions within <pre>,
    # <code>, and <a> elements are ignored. Mentions that reference users that do
    # not exist are ignored.
    #
    # Context options:
    #   :base_url - Used to construct links to user profile pages for each
    #               mention.
    #   :info_url - Used to link to "more info" when someone mentions @mention
    #               or @mentioned.
    #   :username_pattern - Used to provide a custom regular expression to
    #                       identify usernames
    #
    class MentionFilter < Filter
      # Public: Find user @mentions in text.  See
      # MentionFilter#mention_link_filter.
      #
      #   MentionFilter.mentioned_logins_in(text) do |match, login, is_mentioned|
      #     "<a href=...>#{login}</a>"
      #   end
      #
      # text - String text to search.
      #
      # Yields the String match, the String login name, and a Boolean determining
      # if the match = "@mention[ed]".  The yield's return replaces the match in
      # the original text.
      #
      # Returns a String replaced with the return of the block.
      def self.mentioned_logins_in(text, username_pattern = UsernamePattern)
        text.gsub MentionPatterns[username_pattern] do |match|
          login = Regexp.last_match(1)
          yield match, login, MentionLogins.include?(login.downcase)
        end
      end

      # Hash that contains all of the mention patterns used by the pipeline
      MentionPatterns = Hash.new do |hash, key|
        hash[key] = /
          (?:^|\W)                    # beginning of string or non-word char
          @((?>#{key}))  # @username
          (?!\/)                      # without a trailing slash
          (?=
            \.+[ \t\W]|               # dots followed by space or non-word character
            \.+$|                     # dots at end of line
            [^0-9a-zA-Z_.]|           # non-word character except dot
            $                         # end of line
          )
        /ix
      end

      # Default pattern used to extract usernames from text. The value can be
      # overriden by providing the username_pattern variable in the context.
      UsernamePattern = /[a-z0-9][a-z0-9-]*/

      # List of username logins that, when mentioned, link to the blog post
      # about @mentions instead of triggering a real mention.
      MentionLogins = %w[
        mention
        mentions
        mentioned
        mentioning
      ].freeze

      # Don't look for mentions in text nodes that are children of these elements
      IGNORE_PARENTS = %w(pre code a style script).to_set

      def call
        result[:mentioned_usernames] ||= []

        doc.search('.//text()').each do |node|
          content = node.to_html
          next unless content.include?('@')
          next if has_ancestor?(node, IGNORE_PARENTS)
          html = mention_link_filter(content, base_url, info_url, username_pattern)
          next if html == content
          node.replace(html)
        end
        doc
      end

      # The URL to provide when someone @mentions a "mention" name, such
      # as @mention or @mentioned, that will give them more info on mentions.
      def info_url
        context[:info_url] || nil
      end

      def username_pattern
        context[:username_pattern] || UsernamePattern
      end

      # Replace user @mentions in text with links to the mentioned user's
      # profile page.
      #
      # text      - String text to replace @mention usernames in.
      # base_url  - The base URL used to construct user profile URLs.
      # info_url  - The "more info" URL used to link to more info on @mentions.
      #             If nil we don't link @mention or @mentioned.
      # username_pattern  - Regular expression used to identify usernames in
      #                     text
      #
      # Returns a string with @mentions replaced with links. All links have a
      # 'user-mention' class name attached for styling.
      def mention_link_filter(text, _base_url = '/', info_url = nil, username_pattern = UsernamePattern)
        self.class.mentioned_logins_in(text, username_pattern) do |match, login, is_mentioned|
          link =
            if is_mentioned
              link_to_mention_info(login, info_url)
            else
              link_to_mentioned_user(login)
            end

          link ? match.sub("@#{login}", link) : match
        end
      end

      def link_to_mention_info(text, info_url = nil)
        return "@#{text}" if info_url.nil?
        "<a href='#{info_url}' class='user-mention'>" \
          "@#{text}" \
          '</a>'
      end

      def link_to_mentioned_user(login)
        result[:mentioned_usernames] |= [login]

        url = base_url.dup
        url << '/' unless url =~ /[\/~]\z/

        "<a href='#{url << login}' class='user-mention'>" \
          "@#{login}" \
          '</a>'
      end
    end
  end
end