module Twitter
# A module for including Tweet auto-linking in a class. The primary use of this is for helpers/views so they can auto-link
# usernames, lists, hashtags and URLs.
module Autolink extend self
include ActionView::Helpers::TagHelper #tag_options needed by auto_link
# Default CSS class for auto-linked URLs
DEFAULT_URL_CLASS = "tweet-url"
# Default CSS class for auto-linked lists (along with the url class)
DEFAULT_LIST_CLASS = "list-slug"
# Default CSS class for auto-linked usernames (along with the url class)
DEFAULT_USERNAME_CLASS = "username"
# Default CSS class for auto-linked hashtags (along with the url class)
DEFAULT_HASHTAG_CLASS = "hashtag"
# Default target for auto-linked urls (nil will not add a target attribute)
DEFAULT_TARGET = nil
# HTML attribute for robot nofollow behavior (default)
HTML_ATTR_NO_FOLLOW = " rel=\"nofollow\""
HTML_ENTITIES = {
'&' => '&',
'>' => '>',
'<' => '<',
'"' => '"',
"'" => '''
}
def html_escape(text)
text && text.gsub(/[&"'><]/) do |character|
HTML_ENTITIES[character]
end
end
# Add tags around the usernames, lists, hashtags and URLs in the provided text. The
# tags can be controlled with the following entries in the options
# hash:
#
# :url_class:: class to add to all tags
# :list_class:: class to add to list tags
# :username_class:: class to add to username tags
# :hashtag_class:: class to add to hashtag tags
# :username_url_base:: the value for href attribute on username links. The @username (minus the @) will be appended at the end of this.
# :list_url_base:: the value for href attribute on list links. The @username/list (minus the @) will be appended at the end of this.
# :hashtag_url_base:: the value for href attribute on hashtag links. The #hashtag (minus the #) will be appended at the end of this.
# :suppress_lists:: disable auto-linking to lists
# :suppress_no_follow:: Do not add rel="nofollow" to auto-linked items
# :target:: add target="window_name" to auto-linked items
def auto_link(text, options = {})
auto_link_usernames_or_lists(
auto_link_urls_custom(
auto_link_hashtags(text, options),
options),
options)
end
# Add tags around the usernames and lists in the provided text. The
# tags can be controlled with the following entries in the options
# hash:
#
# :url_class:: class to add to all tags
# :list_class:: class to add to list tags
# :username_class:: class to add to username tags
# :username_url_base:: the value for href attribute on username links. The @username (minus the @) will be appended at the end of this.
# :list_url_base:: the value for href attribute on list links. The @username/list (minus the @) will be appended at the end of this.
# :suppress_lists:: disable auto-linking to lists
# :suppress_no_follow:: Do not add rel="nofollow" to auto-linked items
# :target:: add target="window_name" to auto-linked items
def auto_link_usernames_or_lists(text, options = {}) # :yields: list_or_username
options = options.dup
options[:url_class] ||= DEFAULT_URL_CLASS
options[:list_class] ||= DEFAULT_LIST_CLASS
options[:username_class] ||= DEFAULT_USERNAME_CLASS
options[:username_url_base] ||= "http://twitter.com/"
options[:list_url_base] ||= "http://twitter.com/"
options[:target] ||= DEFAULT_TARGET
extra_html = HTML_ATTR_NO_FOLLOW unless options[:suppress_no_follow]
new_text = ""
# this -1 flag allows strings ending in ">" to work
text.split(/[<>]/, -1).each_with_index do |chunk, index|
if index != 0
new_text << ((index % 2 == 0) ? ">" : "<")
end
if index % 4 != 0
new_text << chunk
else
new_text << chunk.gsub(Twitter::Regex[:auto_link_usernames_or_lists]) do
before, at, user, slash_listname, after = $1, $2, $3, $4, $'
if slash_listname && !options[:suppress_lists]
# the link is a list
chunk = list = "#{user}#{slash_listname}"
chunk = yield(list) if block_given?
"#{before}#{at}#{html_escape(chunk)}"
else
if after =~ Twitter::Regex[:end_screen_name_match]
# Followed by something that means we don't autolink
"#{before}#{at}#{user}#{slash_listname}"
else
# this is a screen name
chunk = user
chunk = yield(chunk) if block_given?
"#{before}#{at}#{html_escape(chunk)}"
end
end
end
end
end
new_text
end
# Add tags around the hashtags in the provided text. The
# tags can be controlled with the following entries in the options
# hash:
#
# :url_class:: class to add to all tags
# :hashtag_class:: class to add to hashtag tags
# :hashtag_url_base:: the value for href attribute. The hashtag text (minus the #) will be appended at the end of this.
# :suppress_no_follow:: Do not add rel="nofollow" to auto-linked items
# :target:: add target="window_name" to auto-linked items
def auto_link_hashtags(text, options = {}) # :yields: hashtag_text
options = options.dup
options[:url_class] ||= DEFAULT_URL_CLASS
options[:hashtag_class] ||= DEFAULT_HASHTAG_CLASS
options[:hashtag_url_base] ||= "http://twitter.com/search?q=%23"
options[:target] ||= DEFAULT_TARGET
extra_html = HTML_ATTR_NO_FOLLOW unless options[:suppress_no_follow]
text.gsub(Twitter::Regex[:auto_link_hashtags]) do
before = $1
hash = $2
text = $3
text = yield(text) if block_given?
"#{before}#{html_escape(hash)}#{html_escape(text)}"
end
end
# Add tags around the URLs in the provided text. Any
# elements in the href_options hash will be converted to HTML attributes
# and place in the tag. Unless href_options contains :suppress_no_follow
# the rel="nofollow" attribute will be added.
def auto_link_urls_custom(text, href_options = {})
options = href_options.dup
options[:rel] = "nofollow" unless options.delete(:suppress_no_follow)
text.gsub(Twitter::Regex[:valid_url]) do
all, before, url, protocol, domain, path, query_string = $1, $2, $3, $4, $5, $6, $7
if !protocol.blank?
html_attrs = tag_options(options.stringify_keys) || ""
"#{before}#{html_escape(url)}"
else
all
end
end
end
private
def target_tag(options)
target_option = options[:target]
if target_option.blank?
""
else
"target=\"#{html_escape(target_option)}\""
end
end
end
end