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
|