twitter-text-simpleidn 3.0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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