# frozen_string_literal: true require "set" class HTMLPipeline class NodeFilter # HTML filter that replaces @user mentions with links. Mentions within
,
# , and 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 < NodeFilter
class << self
# Public: Find user @mentions in text. See
# MentionFilter#mention_link_filter.
#
# MentionFilter.mentioned_logins_in(text) do |match, login, is_mentioned|
# "#{login}"
# 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 mentioned_logins_in(text, username_pattern = USERNAME_PATTERN)
text.gsub(MENTION_PATTERNS[username_pattern]) do |match|
login = Regexp.last_match(1)
yield match, login
end
end
end
# Hash that contains all of the mention patterns used by the pipeline
MENTION_PATTERNS = Hash.new do |hash, key|
hash[key] = %r{
(?:^|\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.
USERNAME_PATTERN = /[a-z0-9][a-z0-9-]*/
# Don't look for mentions in text nodes that are children of these elements
IGNORE_PARENTS = ["pre", "code", "a", "style", "script"]
SELECTOR = Selma::Selector.new(match_text_within: "*", ignore_text_within: IGNORE_PARENTS)
def after_initialize
result[:mentioned_usernames] ||= []
end
def selector
SELECTOR
end
def handle_text_chunk(text)
content = text.to_s
return unless content.include?("@")
html = mention_link_filter(content, base_url: base_url, username_pattern: username_pattern)
return if html == content
text.replace(html, as: :html)
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] || USERNAME_PATTERN
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: "/", username_pattern: USERNAME_PATTERN)
self.class.mentioned_logins_in(text, username_pattern) do |match, login|
link = link_to_mentioned_user(base_url, login)
link ? match.sub("@#{login}", link) : match
end
end
def link_to_mentioned_user(base_url, login)
result[:mentioned_usernames] |= [login]
url = base_url.dup
url << "/" unless %r{[/~]\z}.match?(url)
"" \
"@#{login}" \
""
end
end
end
end