twitter-text-simpleidn 3.0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ # Copyright 2018 Twitter, Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+ # http://www.apache.org/licenses/LICENSE-2.0
4
+
5
+ major, minor, _patch = RUBY_VERSION.split('.')
6
+
7
+ $RUBY_1_9 = if major.to_i == 1 && minor.to_i < 9
8
+ # Ruby 1.8 KCODE check. Not needed on 1.9 and later.
9
+ raise("twitter-text requires the $KCODE variable be set to 'UTF8' or 'u'") unless $KCODE[0].chr =~ /u/i
10
+ false
11
+ else
12
+ true
13
+ end
14
+
15
+ %w(
16
+ deprecation
17
+ emoji_regex
18
+ regex
19
+ rewriter
20
+ autolink
21
+ extractor
22
+ unicode
23
+ weighted_range
24
+ configuration
25
+ validation
26
+ hit_highlighter
27
+ ).each do |name|
28
+ require "twitter-text/#{name}"
29
+ end
@@ -0,0 +1,453 @@
1
+ # Copyright 2018 Twitter, Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+ # http://www.apache.org/licenses/LICENSE-2.0
4
+
5
+ # encoding: utf-8
6
+
7
+ require 'set'
8
+ require 'twitter-text/hash_helper'
9
+
10
+ module Twitter
11
+ module TwitterText
12
+ # A module for including Tweet auto-linking in a class. The primary use of this is for helpers/views so they can auto-link
13
+ # usernames, lists, hashtags and URLs.
14
+ module Autolink extend self
15
+ # Default CSS class for auto-linked lists
16
+ DEFAULT_LIST_CLASS = "tweet-url list-slug".freeze
17
+ # Default CSS class for auto-linked usernames
18
+ DEFAULT_USERNAME_CLASS = "tweet-url username".freeze
19
+ # Default CSS class for auto-linked hashtags
20
+ DEFAULT_HASHTAG_CLASS = "tweet-url hashtag".freeze
21
+ # Default CSS class for auto-linked cashtags
22
+ DEFAULT_CASHTAG_CLASS = "tweet-url cashtag".freeze
23
+
24
+ # Default URL base for auto-linked usernames
25
+ DEFAULT_USERNAME_URL_BASE = "https://twitter.com/".freeze
26
+ # Default URL base for auto-linked lists
27
+ DEFAULT_LIST_URL_BASE = "https://twitter.com/".freeze
28
+ # Default URL base for auto-linked hashtags
29
+ DEFAULT_HASHTAG_URL_BASE = "https://twitter.com/search?q=%23".freeze
30
+ # Default URL base for auto-linked cashtags
31
+ DEFAULT_CASHTAG_URL_BASE = "https://twitter.com/search?q=%24".freeze
32
+
33
+ # Default attributes for invisible span tag
34
+ DEFAULT_INVISIBLE_TAG_ATTRS = "style='position:absolute;left:-9999px;'".freeze
35
+
36
+ DEFAULT_OPTIONS = {
37
+ :list_class => DEFAULT_LIST_CLASS,
38
+ :username_class => DEFAULT_USERNAME_CLASS,
39
+ :hashtag_class => DEFAULT_HASHTAG_CLASS,
40
+ :cashtag_class => DEFAULT_CASHTAG_CLASS,
41
+
42
+ :username_url_base => DEFAULT_USERNAME_URL_BASE,
43
+ :list_url_base => DEFAULT_LIST_URL_BASE,
44
+ :hashtag_url_base => DEFAULT_HASHTAG_URL_BASE,
45
+ :cashtag_url_base => DEFAULT_CASHTAG_URL_BASE,
46
+
47
+ :invisible_tag_attrs => DEFAULT_INVISIBLE_TAG_ATTRS
48
+ }.freeze
49
+
50
+ def auto_link_with_json(text, json, options = {})
51
+ # concatenate entities
52
+ entities = json.values().flatten()
53
+
54
+ # map JSON entity to twitter-text entity
55
+ # be careful not to alter arguments received
56
+ entities.map! do |entity|
57
+ entity = HashHelper.symbolize_keys(entity)
58
+ # hashtag
59
+ entity[:hashtag] = entity[:text] if entity[:text]
60
+ entity
61
+ end
62
+
63
+ auto_link_entities(text, entities, options)
64
+ end
65
+
66
+ def auto_link_entities(text, entities, options = {}, &block)
67
+ return text if entities.empty?
68
+
69
+ # NOTE deprecate these attributes not options keys in options hash, then use html_attrs
70
+ options = DEFAULT_OPTIONS.merge(options)
71
+ options[:html_attrs] = extract_html_attrs_from_options!(options)
72
+ options[:html_attrs][:rel] ||= "nofollow" unless options[:suppress_no_follow]
73
+ options[:html_attrs][:target] = "_blank" if options[:target_blank] == true
74
+
75
+ Twitter::TwitterText::Rewriter.rewrite_entities(text.dup, entities) do |entity, chars|
76
+ if entity[:url]
77
+ link_to_url(entity, chars, options, &block)
78
+ elsif entity[:hashtag]
79
+ link_to_hashtag(entity, chars, options, &block)
80
+ elsif entity[:screen_name]
81
+ link_to_screen_name(entity, chars, options, &block)
82
+ elsif entity[:cashtag]
83
+ link_to_cashtag(entity, chars, options, &block)
84
+ end
85
+ end
86
+ end
87
+
88
+ # Add <tt><a></a></tt> tags around the usernames, lists, hashtags and URLs in the provided <tt>text</tt>.
89
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash:
90
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
91
+ # and place in the <tt><a></tt> tag.
92
+ #
93
+ # <tt>:url_class</tt>:: class to add to url <tt><a></tt> tags
94
+ # <tt>:list_class</tt>:: class to add to list <tt><a></tt> tags
95
+ # <tt>:username_class</tt>:: class to add to username <tt><a></tt> tags
96
+ # <tt>:hashtag_class</tt>:: class to add to hashtag <tt><a></tt> tags
97
+ # <tt>:cashtag_class</tt>:: class to add to cashtag <tt><a></tt> tags
98
+ # <tt>:username_url_base</tt>:: the value for <tt>href</tt> attribute on username links. The <tt>@username</tt> (minus the <tt>@</tt>) will be appended at the end of this.
99
+ # <tt>:list_url_base</tt>:: the value for <tt>href</tt> attribute on list links. The <tt>@username/list</tt> (minus the <tt>@</tt>) will be appended at the end of this.
100
+ # <tt>:hashtag_url_base</tt>:: the value for <tt>href</tt> attribute on hashtag links. The <tt>#hashtag</tt> (minus the <tt>#</tt>) will be appended at the end of this.
101
+ # <tt>:cashtag_url_base</tt>:: the value for <tt>href</tt> attribute on cashtag links. The <tt>$cashtag</tt> (minus the <tt>$</tt>) will be appended at the end of this.
102
+ # <tt>:invisible_tag_attrs</tt>:: HTML attribute to add to invisible span tags
103
+ # <tt>:username_include_symbol</tt>:: place the <tt>@</tt> symbol within username and list links
104
+ # <tt>:suppress_lists</tt>:: disable auto-linking to lists
105
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
106
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
107
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
108
+ # <tt>:url_target</tt>:: the value for <tt>target</tt> attribute on URL links.
109
+ # <tt>:target_blank</tt>:: adds <tt>target="_blank"</tt> to all auto_linked items username / hashtag / cashtag links / urls
110
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
111
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
112
+ def auto_link(text, options = {}, &block)
113
+ auto_link_entities(text, Extractor.extract_entities_with_indices(text, :extract_url_without_protocol => false), options, &block)
114
+ end
115
+
116
+ # Add <tt><a></a></tt> tags around the usernames and lists in the provided <tt>text</tt>. The
117
+ # <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
118
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
119
+ # and place in the <tt><a></tt> tag.
120
+ #
121
+ # <tt>:list_class</tt>:: class to add to list <tt><a></tt> tags
122
+ # <tt>:username_class</tt>:: class to add to username <tt><a></tt> tags
123
+ # <tt>:username_url_base</tt>:: the value for <tt>href</tt> attribute on username links. The <tt>@username</tt> (minus the <tt>@</tt>) will be appended at the end of this.
124
+ # <tt>:list_url_base</tt>:: the value for <tt>href</tt> attribute on list links. The <tt>@username/list</tt> (minus the <tt>@</tt>) will be appended at the end of this.
125
+ # <tt>:username_include_symbol</tt>:: place the <tt>@</tt> symbol within username and list links
126
+ # <tt>:suppress_lists</tt>:: disable auto-linking to lists
127
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
128
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
129
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
130
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
131
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
132
+ def auto_link_usernames_or_lists(text, options = {}, &block) # :yields: list_or_username
133
+ auto_link_entities(text, Extractor.extract_mentions_or_lists_with_indices(text), options, &block)
134
+ end
135
+
136
+ # Add <tt><a></a></tt> tags around the hashtags in the provided <tt>text</tt>.
137
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
138
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
139
+ # and place in the <tt><a></tt> tag.
140
+ #
141
+ # <tt>:hashtag_class</tt>:: class to add to hashtag <tt><a></tt> tags
142
+ # <tt>:hashtag_url_base</tt>:: the value for <tt>href</tt> attribute. The hashtag text (minus the <tt>#</tt>) will be appended at the end of this.
143
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
144
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
145
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
146
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
147
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
148
+ def auto_link_hashtags(text, options = {}, &block) # :yields: hashtag_text
149
+ auto_link_entities(text, Extractor.extract_hashtags_with_indices(text), options, &block)
150
+ end
151
+
152
+ # Add <tt><a></a></tt> tags around the cashtags in the provided <tt>text</tt>.
153
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
154
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
155
+ # and place in the <tt><a></tt> tag.
156
+ #
157
+ # <tt>:cashtag_class</tt>:: class to add to cashtag <tt><a></tt> tags
158
+ # <tt>:cashtag_url_base</tt>:: the value for <tt>href</tt> attribute. The cashtag text (minus the <tt>$</tt>) will be appended at the end of this.
159
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
160
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
161
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
162
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
163
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
164
+ def auto_link_cashtags(text, options = {}, &block) # :yields: cashtag_text
165
+ auto_link_entities(text, Extractor.extract_cashtags_with_indices(text), options, &block)
166
+ end
167
+
168
+ # Add <tt><a></a></tt> tags around the URLs in the provided <tt>text</tt>.
169
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
170
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
171
+ # and place in the <tt><a></tt> tag.
172
+ #
173
+ # <tt>:url_class</tt>:: class to add to url <tt><a></tt> tags
174
+ # <tt>:invisible_tag_attrs</tt>:: HTML attribute to add to invisible span tags
175
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
176
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
177
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
178
+ # <tt>:url_target</tt>:: the value for <tt>target</tt> attribute on URL links.
179
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
180
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
181
+ def auto_link_urls(text, options = {}, &block)
182
+ auto_link_entities(text, Extractor.extract_urls_with_indices(text, :extract_url_without_protocol => false), options, &block)
183
+ end
184
+
185
+ # These methods are deprecated, will be removed in future.
186
+ extend Deprecation
187
+
188
+ # <b>Deprecated</b>: Please use auto_link_urls instead.
189
+ # Add <tt><a></a></tt> tags around the URLs in the provided <tt>text</tt>.
190
+ # Any elements in the <tt>href_options</tt> hash will be converted to HTML attributes
191
+ # and place in the <tt><a></tt> tag.
192
+ # Unless <tt>href_options</tt> contains <tt>:suppress_no_follow</tt>
193
+ # the <tt>rel="nofollow"</tt> attribute will be added.
194
+ alias :auto_link_urls_custom :auto_link_urls
195
+ deprecate :auto_link_urls_custom, :auto_link_urls
196
+
197
+ private
198
+
199
+ HTML_ENTITIES = {
200
+ '&' => '&amp;',
201
+ '>' => '&gt;',
202
+ '<' => '&lt;',
203
+ '"' => '&quot;',
204
+ "'" => '&#39;'
205
+ }
206
+
207
+ def html_escape(text)
208
+ text && text.to_s.gsub(/[&"'><]/) do |character|
209
+ HTML_ENTITIES[character]
210
+ end
211
+ end
212
+
213
+ # NOTE We will make this private in future.
214
+ public :html_escape
215
+
216
+ # Options which should not be passed as HTML attributes
217
+ OPTIONS_NOT_ATTRIBUTES = Set.new([
218
+ :url_class, :list_class, :username_class, :hashtag_class, :cashtag_class,
219
+ :username_url_base, :list_url_base, :hashtag_url_base, :cashtag_url_base,
220
+ :username_url_block, :list_url_block, :hashtag_url_block, :cashtag_url_block, :link_url_block,
221
+ :username_include_symbol, :suppress_lists, :suppress_no_follow, :url_entities,
222
+ :invisible_tag_attrs, :symbol_tag, :text_with_symbol_tag, :url_target, :target_blank,
223
+ :link_attribute_block, :link_text_block
224
+ ]).freeze
225
+
226
+ def extract_html_attrs_from_options!(options)
227
+ html_attrs = {}
228
+ options.reject! do |key, value|
229
+ unless OPTIONS_NOT_ATTRIBUTES.include?(key)
230
+ html_attrs[key] = value
231
+ true
232
+ end
233
+ end
234
+ html_attrs
235
+ end
236
+
237
+ def url_entities_hash(url_entities)
238
+ (url_entities || {}).inject({}) do |entities, entity|
239
+ # be careful not to alter arguments received
240
+ _entity = HashHelper.symbolize_keys(entity)
241
+ entities[_entity[:url]] = _entity
242
+ entities
243
+ end
244
+ end
245
+
246
+ def link_to_url(entity, chars, options = {})
247
+ url = entity[:url]
248
+
249
+ href = if options[:link_url_block]
250
+ options[:link_url_block].call(url)
251
+ else
252
+ url
253
+ end
254
+
255
+ # NOTE auto link to urls do not use any default values and options
256
+ # like url_class but use suppress_no_follow.
257
+ html_attrs = options[:html_attrs].dup
258
+ html_attrs[:class] = options[:url_class] if options.key?(:url_class)
259
+
260
+ # add target attribute only if :url_target is specified
261
+ html_attrs[:target] = options[:url_target] if options.key?(:url_target)
262
+
263
+ url_entities = url_entities_hash(options[:url_entities])
264
+
265
+ # use entity from urlEntities if available
266
+ url_entity = url_entities[url] || entity
267
+ link_text = if url_entity[:display_url]
268
+ html_attrs[:title] ||= url_entity[:expanded_url]
269
+ link_url_with_entity(url_entity, options)
270
+ else
271
+ html_escape(url)
272
+ end
273
+
274
+ link_to_text(entity, link_text, href, html_attrs, options)
275
+ end
276
+
277
+ def link_url_with_entity(entity, options)
278
+ display_url = entity[:display_url]
279
+ expanded_url = entity[:expanded_url]
280
+ invisible_tag_attrs = options[:invisible_tag_attrs] || DEFAULT_INVISIBLE_TAG_ATTRS
281
+
282
+ # Goal: If a user copies and pastes a tweet containing t.co'ed link, the resulting paste
283
+ # should contain the full original URL (expanded_url), not the display URL.
284
+ #
285
+ # Method: Whenever possible, we actually emit HTML that contains expanded_url, and use
286
+ # font-size:0 to hide those parts that should not be displayed (because they are not part of display_url).
287
+ # Elements with font-size:0 get copied even though they are not visible.
288
+ # Note that display:none doesn't work here. Elements with display:none don't get copied.
289
+ #
290
+ # Additionally, we want to *display* ellipses, but we don't want them copied. To make this happen we
291
+ # wrap the ellipses in a tco-ellipsis class and provide an onCopy handler that sets display:none on
292
+ # everything with the tco-ellipsis class.
293
+ #
294
+ # Exception: pic.twitter.com images, for which expandedUrl = "https://twitter.com/username/status/1234/photo/1
295
+ # For those URLs, display_url is not a substring of expanded_url, so we don't do anything special to render the elided parts.
296
+ # For a pic.twitter.com URL, the only elided part will be the "https://", so this is fine.
297
+ display_url_sans_ellipses = display_url.gsub("…", "")
298
+
299
+ if expanded_url.include?(display_url_sans_ellipses)
300
+ before_display_url, after_display_url = expanded_url.split(display_url_sans_ellipses, 2)
301
+ preceding_ellipsis = /\A…/.match(display_url).to_s
302
+ following_ellipsis = /…\z/.match(display_url).to_s
303
+
304
+ # As an example: The user tweets "hi http://longdomainname.com/foo"
305
+ # This gets shortened to "hi http://t.co/xyzabc", with display_url = "…nname.com/foo"
306
+ # This will get rendered as:
307
+ # <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
308
+ # …
309
+ # <!-- There's a chance the onCopy event handler might not fire. In case that happens,
310
+ # we include an &nbsp; here so that the … doesn't bump up against the URL and ruin it.
311
+ # The &nbsp; is inside the tco-ellipsis span so that when the onCopy handler *does*
312
+ # fire, it doesn't get copied. Otherwise the copied text would have two spaces in a row,
313
+ # e.g. "hi http://longdomainname.com/foo".
314
+ # <span style='font-size:0'>&nbsp;</span>
315
+ # </span>
316
+ # <span style='font-size:0'> <!-- This stuff should get copied but not displayed -->
317
+ # http://longdomai
318
+ # </span>
319
+ # <span class='js-display-url'> <!-- This stuff should get displayed *and* copied -->
320
+ # nname.com/foo
321
+ # </span>
322
+ # <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
323
+ # <span style='font-size:0'>&nbsp;</span>
324
+ # …
325
+ # </span>
326
+ %(<span class="tco-ellipsis">#{preceding_ellipsis}<span #{invisible_tag_attrs}>&nbsp;</span></span>) <<
327
+ %(<span #{invisible_tag_attrs}>#{html_escape(before_display_url)}</span>) <<
328
+ %(<span class="js-display-url">#{html_escape(display_url_sans_ellipses)}</span>) <<
329
+ %(<span #{invisible_tag_attrs}>#{html_escape(after_display_url)}</span>) <<
330
+ %(<span class="tco-ellipsis"><span #{invisible_tag_attrs}>&nbsp;</span>#{following_ellipsis}</span>)
331
+ else
332
+ html_escape(display_url)
333
+ end
334
+ end
335
+
336
+ def link_to_hashtag(entity, chars, options = {})
337
+ hash = chars[entity[:indices].first]
338
+ hashtag = entity[:hashtag]
339
+ hashtag = yield(hashtag) if block_given?
340
+ hashtag_class = options[:hashtag_class].to_s
341
+
342
+ if hashtag.match Twitter::TwitterText::Regex::REGEXEN[:rtl_chars]
343
+ hashtag_class += ' rtl'
344
+ end
345
+
346
+ href = if options[:hashtag_url_block]
347
+ options[:hashtag_url_block].call(hashtag)
348
+ else
349
+ "#{options[:hashtag_url_base]}#{hashtag}"
350
+ end
351
+
352
+ html_attrs = {
353
+ :class => hashtag_class,
354
+ # FIXME As our conformance test, hash in title should be half-width,
355
+ # this should be bug of conformance data.
356
+ :title => "##{hashtag}"
357
+ }.merge(options[:html_attrs])
358
+
359
+ link_to_text_with_symbol(entity, hash, hashtag, href, html_attrs, options)
360
+ end
361
+
362
+ def link_to_cashtag(entity, chars, options = {})
363
+ dollar = chars[entity[:indices].first]
364
+ cashtag = entity[:cashtag]
365
+ cashtag = yield(cashtag) if block_given?
366
+
367
+ href = if options[:cashtag_url_block]
368
+ options[:cashtag_url_block].call(cashtag)
369
+ else
370
+ "#{options[:cashtag_url_base]}#{cashtag}"
371
+ end
372
+
373
+ html_attrs = {
374
+ :class => "#{options[:cashtag_class]}",
375
+ :title => "$#{cashtag}"
376
+ }.merge(options[:html_attrs])
377
+
378
+ link_to_text_with_symbol(entity, dollar, cashtag, href, html_attrs, options)
379
+ end
380
+
381
+ def link_to_screen_name(entity, chars, options = {})
382
+ name = "#{entity[:screen_name]}#{entity[:list_slug]}"
383
+
384
+ chunk = name.dup
385
+ chunk = yield(chunk) if block_given?
386
+
387
+ at = chars[entity[:indices].first]
388
+
389
+ html_attrs = options[:html_attrs].dup
390
+
391
+ if entity[:list_slug] && !entity[:list_slug].empty? && !options[:suppress_lists]
392
+ href = if options[:list_url_block]
393
+ options[:list_url_block].call(name)
394
+ else
395
+ "#{options[:list_url_base]}#{name}"
396
+ end
397
+ html_attrs[:class] ||= "#{options[:list_class]}"
398
+ else
399
+ href = if options[:username_url_block]
400
+ options[:username_url_block].call(chunk)
401
+ else
402
+ "#{options[:username_url_base]}#{name}"
403
+ end
404
+ html_attrs[:class] ||= "#{options[:username_class]}"
405
+ end
406
+
407
+ link_to_text_with_symbol(entity, at, chunk, href, html_attrs, options)
408
+ end
409
+
410
+ def link_to_text_with_symbol(entity, symbol, text, href, attributes = {}, options = {})
411
+ tagged_symbol = options[:symbol_tag] ? "<#{options[:symbol_tag]}>#{symbol}</#{options[:symbol_tag]}>" : symbol
412
+ text = html_escape(text)
413
+ tagged_text = options[:text_with_symbol_tag] ? "<#{options[:text_with_symbol_tag]}>#{text}</#{options[:text_with_symbol_tag]}>" : text
414
+ if options[:username_include_symbol] || symbol !~ Twitter::TwitterText::Regex::REGEXEN[:at_signs]
415
+ "#{link_to_text(entity, tagged_symbol + tagged_text, href, attributes, options)}"
416
+ else
417
+ "#{tagged_symbol}#{link_to_text(entity, tagged_text, href, attributes, options)}"
418
+ end
419
+ end
420
+
421
+ def link_to_text(entity, text, href, attributes = {}, options = {})
422
+ attributes[:href] = href
423
+ options[:link_attribute_block].call(entity, attributes) if options[:link_attribute_block]
424
+ text = options[:link_text_block].call(entity, text) if options[:link_text_block]
425
+ %(<a#{tag_attrs(attributes)}>#{text}</a>)
426
+ end
427
+
428
+ BOOLEAN_ATTRIBUTES = Set.new([:disabled, :readonly, :multiple, :checked]).freeze
429
+
430
+ def tag_attrs(attributes)
431
+ attributes.keys.sort_by{|k| k.to_s}.inject("") do |attrs, key|
432
+ value = attributes[key]
433
+
434
+ if BOOLEAN_ATTRIBUTES.include?(key)
435
+ value = value ? key : nil
436
+ end
437
+
438
+ unless value.nil?
439
+ value = case value
440
+ when Array
441
+ value.compact.join(" ")
442
+ else
443
+ value
444
+ end
445
+ attrs << %( #{html_escape(key)}="#{html_escape(value)}")
446
+ end
447
+
448
+ attrs
449
+ end
450
+ end
451
+ end
452
+ end
453
+ end