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