omg-actionview 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/app/assets/javascripts/rails-ujs.esm.js +686 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +316 -0
  8. data/lib/action_view/buffers.rb +165 -0
  9. data/lib/action_view/cache_expiry.rb +69 -0
  10. data/lib/action_view/context.rb +32 -0
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
  12. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  13. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  14. data/lib/action_view/dependency_tracker.rb +41 -0
  15. data/lib/action_view/deprecator.rb +7 -0
  16. data/lib/action_view/digestor.rb +130 -0
  17. data/lib/action_view/flows.rb +75 -0
  18. data/lib/action_view/gem_version.rb +17 -0
  19. data/lib/action_view/helpers/active_model_helper.rb +54 -0
  20. data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
  21. data/lib/action_view/helpers/asset_url_helper.rb +473 -0
  22. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  23. data/lib/action_view/helpers/cache_helper.rb +315 -0
  24. data/lib/action_view/helpers/capture_helper.rb +236 -0
  25. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  26. data/lib/action_view/helpers/controller_helper.rb +42 -0
  27. data/lib/action_view/helpers/csp_helper.rb +26 -0
  28. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  29. data/lib/action_view/helpers/date_helper.rb +1266 -0
  30. data/lib/action_view/helpers/debug_helper.rb +38 -0
  31. data/lib/action_view/helpers/form_helper.rb +2765 -0
  32. data/lib/action_view/helpers/form_options_helper.rb +927 -0
  33. data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
  34. data/lib/action_view/helpers/javascript_helper.rb +96 -0
  35. data/lib/action_view/helpers/number_helper.rb +165 -0
  36. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  37. data/lib/action_view/helpers/rendering_helper.rb +218 -0
  38. data/lib/action_view/helpers/sanitize_helper.rb +201 -0
  39. data/lib/action_view/helpers/tag_helper.rb +621 -0
  40. data/lib/action_view/helpers/tags/base.rb +138 -0
  41. data/lib/action_view/helpers/tags/check_box.rb +65 -0
  42. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  43. data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
  44. data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
  45. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  46. data/lib/action_view/helpers/tags/collection_select.rb +33 -0
  47. data/lib/action_view/helpers/tags/color_field.rb +26 -0
  48. data/lib/action_view/helpers/tags/date_field.rb +14 -0
  49. data/lib/action_view/helpers/tags/date_select.rb +75 -0
  50. data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
  51. data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
  52. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  53. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  54. data/lib/action_view/helpers/tags/file_field.rb +26 -0
  55. data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
  56. data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/label.rb +84 -0
  58. data/lib/action_view/helpers/tags/month_field.rb +14 -0
  59. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  60. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  61. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  62. data/lib/action_view/helpers/tags/radio_button.rb +32 -0
  63. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  64. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  65. data/lib/action_view/helpers/tags/select.rb +45 -0
  66. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  67. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  68. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  69. data/lib/action_view/helpers/tags/text_field.rb +33 -0
  70. data/lib/action_view/helpers/tags/time_field.rb +23 -0
  71. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  72. data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
  73. data/lib/action_view/helpers/tags/translator.rb +39 -0
  74. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  75. data/lib/action_view/helpers/tags/week_field.rb +14 -0
  76. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  77. data/lib/action_view/helpers/tags.rb +47 -0
  78. data/lib/action_view/helpers/text_helper.rb +568 -0
  79. data/lib/action_view/helpers/translation_helper.rb +161 -0
  80. data/lib/action_view/helpers/url_helper.rb +812 -0
  81. data/lib/action_view/helpers.rb +68 -0
  82. data/lib/action_view/layouts.rb +434 -0
  83. data/lib/action_view/locale/en.yml +56 -0
  84. data/lib/action_view/log_subscriber.rb +132 -0
  85. data/lib/action_view/lookup_context.rb +299 -0
  86. data/lib/action_view/model_naming.rb +14 -0
  87. data/lib/action_view/path_registry.rb +57 -0
  88. data/lib/action_view/path_set.rb +84 -0
  89. data/lib/action_view/railtie.rb +132 -0
  90. data/lib/action_view/record_identifier.rb +118 -0
  91. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  92. data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
  93. data/lib/action_view/render_parser.rb +40 -0
  94. data/lib/action_view/renderer/abstract_renderer.rb +186 -0
  95. data/lib/action_view/renderer/collection_renderer.rb +204 -0
  96. data/lib/action_view/renderer/object_renderer.rb +34 -0
  97. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
  98. data/lib/action_view/renderer/partial_renderer.rb +267 -0
  99. data/lib/action_view/renderer/renderer.rb +107 -0
  100. data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
  101. data/lib/action_view/renderer/template_renderer.rb +115 -0
  102. data/lib/action_view/rendering.rb +190 -0
  103. data/lib/action_view/routing_url_for.rb +149 -0
  104. data/lib/action_view/tasks/cache_digests.rake +25 -0
  105. data/lib/action_view/template/error.rb +264 -0
  106. data/lib/action_view/template/handlers/builder.rb +25 -0
  107. data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
  108. data/lib/action_view/template/handlers/erb.rb +157 -0
  109. data/lib/action_view/template/handlers/html.rb +11 -0
  110. data/lib/action_view/template/handlers/raw.rb +11 -0
  111. data/lib/action_view/template/handlers.rb +66 -0
  112. data/lib/action_view/template/html.rb +33 -0
  113. data/lib/action_view/template/inline.rb +22 -0
  114. data/lib/action_view/template/raw_file.rb +25 -0
  115. data/lib/action_view/template/renderable.rb +30 -0
  116. data/lib/action_view/template/resolver.rb +212 -0
  117. data/lib/action_view/template/sources/file.rb +17 -0
  118. data/lib/action_view/template/sources.rb +13 -0
  119. data/lib/action_view/template/text.rb +32 -0
  120. data/lib/action_view/template/types.rb +50 -0
  121. data/lib/action_view/template.rb +580 -0
  122. data/lib/action_view/template_details.rb +66 -0
  123. data/lib/action_view/template_path.rb +66 -0
  124. data/lib/action_view/test_case.rb +449 -0
  125. data/lib/action_view/testing/resolvers.rb +44 -0
  126. data/lib/action_view/unbound_template.rb +67 -0
  127. data/lib/action_view/version.rb +10 -0
  128. data/lib/action_view/view_paths.rb +117 -0
  129. data/lib/action_view.rb +104 -0
  130. metadata +275 -0
@@ -0,0 +1,568 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/filters"
4
+ require "active_support/core_ext/string/access"
5
+ require "active_support/core_ext/array/extract_options"
6
+ require "action_view/helpers/sanitize_helper"
7
+ require "action_view/helpers/tag_helper"
8
+ require "action_view/helpers/output_safety_helper"
9
+
10
+ module ActionView
11
+ module Helpers # :nodoc:
12
+ # = Action View Text \Helpers
13
+ #
14
+ # The TextHelper module provides a set of methods for filtering, formatting
15
+ # and transforming strings, which can reduce the amount of inline Ruby code in
16
+ # your views. These helper methods extend Action View making them callable
17
+ # within your template files.
18
+ #
19
+ # ==== Sanitization
20
+ #
21
+ # Most text helpers that generate HTML output sanitize the given input by default,
22
+ # but do not escape it. This means HTML tags will appear in the page but all malicious
23
+ # code will be removed. Let's look at some examples using the +simple_format+ method:
24
+ #
25
+ # simple_format('<a href="http://example.com/">Example</a>')
26
+ # # => "<p><a href=\"http://example.com/\">Example</a></p>"
27
+ #
28
+ # simple_format('<a href="javascript:alert(\'no!\')">Example</a>')
29
+ # # => "<p><a>Example</a></p>"
30
+ #
31
+ # If you want to escape all content, you should invoke the +h+ method before
32
+ # calling the text helper.
33
+ #
34
+ # simple_format h('<a href="http://example.com/">Example</a>')
35
+ # # => "<p>&lt;a href=\"http://example.com/\"&gt;Example&lt;/a&gt;</p>"
36
+ module TextHelper
37
+ extend ActiveSupport::Concern
38
+
39
+ include SanitizeHelper
40
+ include TagHelper
41
+ include OutputSafetyHelper
42
+
43
+ # The preferred method of outputting text in your views is to use the
44
+ # <tt><%= "text" %></tt> eRuby syntax. The regular +puts+ and +print+ methods
45
+ # do not operate as expected in an eRuby code block. If you absolutely must
46
+ # output text within a non-output code block (i.e., <tt><% %></tt>), you
47
+ # can use the +concat+ method.
48
+ #
49
+ # <% concat "hello" %> is equivalent to <%= "hello" %>
50
+ #
51
+ # <%
52
+ # unless signed_in?
53
+ # concat link_to("Sign In", action: :sign_in)
54
+ # end
55
+ # %>
56
+ #
57
+ # is equivalent to
58
+ #
59
+ # <% unless signed_in? %>
60
+ # <%= link_to "Sign In", action: :sign_in %>
61
+ # <% end %>
62
+ #
63
+ def concat(string)
64
+ output_buffer << string
65
+ end
66
+
67
+ def safe_concat(string)
68
+ output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string)
69
+ end
70
+
71
+ # Truncates +text+ if it is longer than a specified +:length+. If +text+
72
+ # is truncated, an omission marker will be appended to the result for a
73
+ # total length not exceeding +:length+.
74
+ #
75
+ # You can also pass a block to render and append extra content after the
76
+ # omission marker when +text+ is truncated. However, this content _can_
77
+ # cause the total length to exceed +:length+ characters.
78
+ #
79
+ # The result will be escaped unless <tt>escape: false</tt> is specified.
80
+ # In any case, the result will be marked HTML-safe. Care should be taken
81
+ # if +text+ might contain HTML tags or entities, because truncation could
82
+ # produce invalid HTML, such as unbalanced or incomplete tags.
83
+ #
84
+ # ==== Options
85
+ #
86
+ # [+:length+]
87
+ # The maximum number of characters that should be returned, excluding
88
+ # any extra content from the block. Defaults to 30.
89
+ #
90
+ # [+:omission+]
91
+ # The string to append after truncating. Defaults to <tt>"..."</tt>.
92
+ #
93
+ # [+:separator+]
94
+ # A string or regexp used to find a breaking point at which to truncate.
95
+ # By default, truncation can occur at any character in +text+.
96
+ #
97
+ # [+:escape+]
98
+ # Whether to escape the result. Defaults to true.
99
+ #
100
+ # ==== Examples
101
+ #
102
+ # truncate("Once upon a time in a world far far away")
103
+ # # => "Once upon a time in a world..."
104
+ #
105
+ # truncate("Once upon a time in a world far far away", length: 17)
106
+ # # => "Once upon a ti..."
107
+ #
108
+ # truncate("Once upon a time in a world far far away", length: 17, separator: ' ')
109
+ # # => "Once upon a..."
110
+ #
111
+ # truncate("And they found that many people were sleeping better.", length: 25, omission: '... (continued)')
112
+ # # => "And they f... (continued)"
113
+ #
114
+ # truncate("<p>Once upon a time in a world far far away</p>")
115
+ # # => "&lt;p&gt;Once upon a time in a wo..."
116
+ #
117
+ # truncate("<p>Once upon a time in a world far far away</p>", escape: false)
118
+ # # => "<p>Once upon a time in a wo..."
119
+ #
120
+ # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" }
121
+ # # => "Once upon a time in a world...<a href=\"#\">Continue</a>"
122
+ def truncate(text, options = {}, &block)
123
+ if text
124
+ length = options.fetch(:length, 30)
125
+
126
+ content = text.truncate(length, options)
127
+ content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content)
128
+ content << capture(&block) if block_given? && text.length > length
129
+ content
130
+ end
131
+ end
132
+
133
+ # Highlights occurrences of +phrases+ in +text+ by formatting them with a
134
+ # highlighter string. +phrases+ can be one or more strings or regular
135
+ # expressions. The result will be marked HTML safe. By default, +text+ is
136
+ # sanitized before highlighting to prevent possible XSS attacks.
137
+ #
138
+ # If a block is specified, it will be used instead of the highlighter
139
+ # string. Each occurrence of a phrase will be passed to the block, and its
140
+ # return value will be inserted into the final result.
141
+ #
142
+ # ==== Options
143
+ #
144
+ # [+:highlighter+]
145
+ # The highlighter string. Uses <tt>\1</tt> as the placeholder for a
146
+ # phrase, similar to +String#sub+. Defaults to <tt>"<mark>\1</mark>"</tt>.
147
+ # This option is ignored if a block is specified.
148
+ #
149
+ # [+:sanitize+]
150
+ # Whether to sanitize +text+ before highlighting. Defaults to true.
151
+ #
152
+ # ==== Examples
153
+ #
154
+ # highlight('You searched for: rails', 'rails')
155
+ # # => "You searched for: <mark>rails</mark>"
156
+ #
157
+ # highlight('You searched for: rails', /for|rails/)
158
+ # # => "You searched <mark>for</mark>: <mark>rails</mark>"
159
+ #
160
+ # highlight('You searched for: ruby, rails, dhh', 'actionpack')
161
+ # # => "You searched for: ruby, rails, dhh"
162
+ #
163
+ # highlight('You searched for: rails', ['for', 'rails'], highlighter: '<em>\1</em>')
164
+ # # => "You searched <em>for</em>: <em>rails</em>"
165
+ #
166
+ # highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
167
+ # # => "You searched for: <a href=\"search?q=rails\">rails</a>"
168
+ #
169
+ # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
170
+ # # => "You searched for: <a href=\"search?q=rails\">rails</a>"
171
+ #
172
+ # highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
173
+ # # => "<a href=\"javascript:alert('no!')\">ruby</a> on <mark>rails</mark>"
174
+ def highlight(text, phrases, options = {}, &block)
175
+ text = sanitize(text) if options.fetch(:sanitize, true)
176
+
177
+ if text.blank? || phrases.blank?
178
+ text || ""
179
+ else
180
+ patterns = Array(phrases).map { |phrase| Regexp === phrase ? phrase : Regexp.escape(phrase) }
181
+ pattern = /(#{patterns.join("|")})/i
182
+ highlighter = options.fetch(:highlighter, '<mark>\1</mark>') unless block
183
+
184
+ text.scan(/<[^>]*|[^<]+/).each do |segment|
185
+ if !segment.start_with?("<")
186
+ if block
187
+ segment.gsub!(pattern, &block)
188
+ else
189
+ segment.gsub!(pattern, highlighter)
190
+ end
191
+ end
192
+ end.join
193
+ end.html_safe
194
+ end
195
+
196
+ # Extracts the first occurrence of +phrase+ plus surrounding text from
197
+ # +text+. An omission marker is prepended / appended if the start / end of
198
+ # the result does not coincide with the start / end of +text+. The result
199
+ # is always stripped in any case. Returns +nil+ if +phrase+ isn't found.
200
+ #
201
+ # ==== Options
202
+ #
203
+ # [+:radius+]
204
+ # The number of characters (or tokens — see +:separator+ option) around
205
+ # +phrase+ to include in the result. Defaults to 100.
206
+ #
207
+ # [+:omission+]
208
+ # The marker to prepend / append when the start / end of the excerpt
209
+ # does not coincide with the start / end of +text+. Defaults to
210
+ # <tt>"..."</tt>.
211
+ #
212
+ # [+:separator+]
213
+ # The separator between tokens to count for +:radius+. Defaults to
214
+ # <tt>""</tt>, which treats each character as a token.
215
+ #
216
+ # ==== Examples
217
+ #
218
+ # excerpt('This is an example', 'an', radius: 5)
219
+ # # => "...s is an exam..."
220
+ #
221
+ # excerpt('This is an example', 'is', radius: 5)
222
+ # # => "This is a..."
223
+ #
224
+ # excerpt('This is an example', 'is')
225
+ # # => "This is an example"
226
+ #
227
+ # excerpt('This next thing is an example', 'ex', radius: 2)
228
+ # # => "...next..."
229
+ #
230
+ # excerpt('This is also an example', 'an', radius: 8, omission: '<chop> ')
231
+ # # => "<chop> is also an example"
232
+ #
233
+ # excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1)
234
+ # # => "...a very beautiful..."
235
+ def excerpt(text, phrase, options = {})
236
+ return unless text && phrase
237
+
238
+ separator = options.fetch(:separator, nil) || ""
239
+ case phrase
240
+ when Regexp
241
+ regex = phrase
242
+ else
243
+ regex = /#{Regexp.escape(phrase)}/i
244
+ end
245
+
246
+ return unless matches = text.match(regex)
247
+ phrase = matches[0]
248
+
249
+ unless separator.empty?
250
+ text.split(separator).each do |value|
251
+ if value.match?(regex)
252
+ phrase = value
253
+ break
254
+ end
255
+ end
256
+ end
257
+
258
+ first_part, second_part = text.split(phrase, 2)
259
+
260
+ prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
261
+ postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
262
+
263
+ affix = [first_part, separator, phrase, separator, second_part].join.strip
264
+ [prefix, affix, postfix].join
265
+ end
266
+
267
+ # Attempts to pluralize the +singular+ word unless +count+ is 1. If
268
+ # +plural+ is supplied, it will use that when count is > 1, otherwise
269
+ # it will use the Inflector to determine the plural form for the given locale,
270
+ # which defaults to +I18n.locale+.
271
+ #
272
+ # The word will be pluralized using rules defined for the locale
273
+ # (you must define your own inflection rules for languages other than English).
274
+ # See ActiveSupport::Inflector.pluralize
275
+ #
276
+ # pluralize(1, 'person')
277
+ # # => "1 person"
278
+ #
279
+ # pluralize(2, 'person')
280
+ # # => "2 people"
281
+ #
282
+ # pluralize(3, 'person', plural: 'users')
283
+ # # => "3 users"
284
+ #
285
+ # pluralize(0, 'person')
286
+ # # => "0 people"
287
+ #
288
+ # pluralize(2, 'Person', locale: :de)
289
+ # # => "2 Personen"
290
+ def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
291
+ word = if count == 1 || count.to_s.match?(/^1(\.0+)?$/)
292
+ singular
293
+ else
294
+ plural || singular.pluralize(locale)
295
+ end
296
+
297
+ "#{count || 0} #{word}"
298
+ end
299
+
300
+ # Wraps the +text+ into lines no longer than +line_width+ width. This method
301
+ # breaks on the first whitespace character that does not exceed +line_width+
302
+ # (which is 80 by default).
303
+ #
304
+ # word_wrap('Once upon a time')
305
+ # # => "Once upon a time"
306
+ #
307
+ # word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...')
308
+ # # => "Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined..."
309
+ #
310
+ # word_wrap('Once upon a time', line_width: 8)
311
+ # # => "Once\nupon a\ntime"
312
+ #
313
+ # word_wrap('Once upon a time', line_width: 1)
314
+ # # => "Once\nupon\na\ntime"
315
+ #
316
+ # You can also specify a custom +break_sequence+ ("\n" by default):
317
+ #
318
+ # word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n")
319
+ # # => "Once\r\nupon\r\na\r\ntime"
320
+ def word_wrap(text, line_width: 80, break_sequence: "\n")
321
+ return +"" if text.empty?
322
+
323
+ # Match up to `line_width` characters, followed by one of
324
+ # (1) non-newline whitespace plus an optional newline
325
+ # (2) the end of the string, ignoring any trailing newlines
326
+ # (3) a newline
327
+ #
328
+ # -OR-
329
+ #
330
+ # Match an empty line
331
+ pattern = /(.{1,#{line_width}})(?:[^\S\n]+\n?|\n*\Z|\n)|\n/
332
+
333
+ text.gsub(pattern, "\\1#{break_sequence}").chomp!(break_sequence)
334
+ end
335
+
336
+ # Returns +text+ transformed into HTML using simple formatting rules.
337
+ # Two or more consecutive newlines (<tt>\n\n</tt> or <tt>\r\n\r\n</tt>) are
338
+ # considered a paragraph and wrapped in <tt><p></tt> tags. One newline
339
+ # (<tt>\n</tt> or <tt>\r\n</tt>) is considered a linebreak and a
340
+ # <tt><br /></tt> tag is appended. This method does not remove the
341
+ # newlines from the +text+.
342
+ #
343
+ # You can pass any HTML attributes into <tt>html_options</tt>. These
344
+ # will be added to all created paragraphs.
345
+ #
346
+ # ==== Options
347
+ # * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
348
+ # * <tt>:sanitize_options</tt> - Any extra options you want appended to the sanitize.
349
+ # * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt>
350
+ #
351
+ # ==== Examples
352
+ # my_text = "Here is some basic text...\n...with a line break."
353
+ #
354
+ # simple_format(my_text)
355
+ # # => "<p>Here is some basic text...\n<br />...with a line break.</p>"
356
+ #
357
+ # simple_format(my_text, {}, wrapper_tag: "div")
358
+ # # => "<div>Here is some basic text...\n<br />...with a line break.</div>"
359
+ #
360
+ # more_text = "We want to put a paragraph...\n\n...right there."
361
+ #
362
+ # simple_format(more_text)
363
+ # # => "<p>We want to put a paragraph...</p>\n\n<p>...right there.</p>"
364
+ #
365
+ # simple_format("Look ma! A class!", class: 'description')
366
+ # # => "<p class='description'>Look ma! A class!</p>"
367
+ #
368
+ # simple_format("<blink>Unblinkable.</blink>")
369
+ # # => "<p>Unblinkable.</p>"
370
+ #
371
+ # simple_format("<blink>Blinkable!</blink> It's true.", {}, sanitize: false)
372
+ # # => "<p><blink>Blinkable!</blink> It's true.</p>"
373
+ #
374
+ # simple_format("<a target=\"_blank\" href=\"http://example.com\">Continue</a>", {}, { sanitize_options: { attributes: %w[target href] } })
375
+ # # => "<p><a target=\"_blank\" href=\"http://example.com\">Continue</a></p>"
376
+ def simple_format(text, html_options = {}, options = {})
377
+ wrapper_tag = options[:wrapper_tag] || "p"
378
+
379
+ text = sanitize(text, options.fetch(:sanitize_options, {})) if options.fetch(:sanitize, true)
380
+ paragraphs = split_paragraphs(text)
381
+
382
+ if paragraphs.empty?
383
+ content_tag(wrapper_tag, nil, html_options)
384
+ else
385
+ paragraphs.map! { |paragraph|
386
+ content_tag(wrapper_tag, raw(paragraph), html_options)
387
+ }.join("\n\n").html_safe
388
+ end
389
+ end
390
+
391
+ # Creates a Cycle object whose +to_s+ method cycles through elements of an
392
+ # array every time it is called. This can be used for example, to alternate
393
+ # classes for table rows. You can use named cycles to allow nesting in loops.
394
+ # Passing a Hash as the last parameter with a <tt>:name</tt> key will create a
395
+ # named cycle. The default name for a cycle without a +:name+ key is
396
+ # <tt>"default"</tt>. You can manually reset a cycle by calling reset_cycle
397
+ # and passing the name of the cycle. The current cycle string can be obtained
398
+ # anytime using the current_cycle method.
399
+ #
400
+ # <%# Alternate CSS classes for even and odd numbers... %>
401
+ # <% @items = [1,2,3,4] %>
402
+ # <table>
403
+ # <% @items.each do |item| %>
404
+ # <tr class="<%= cycle("odd", "even") -%>">
405
+ # <td><%= item %></td>
406
+ # </tr>
407
+ # <% end %>
408
+ # </table>
409
+ #
410
+ #
411
+ # <%# Cycle CSS classes for rows, and text colors for values within each row %>
412
+ # <% @items = [
413
+ # { first: "Robert", middle: "Daniel", last: "James" },
414
+ # { first: "Emily", middle: "Shannon", maiden: "Pike", last: "Hicks" },
415
+ # { first: "June", middle: "Dae", last: "Jones" },
416
+ # ] %>
417
+ # <% @items.each do |item| %>
418
+ # <tr class="<%= cycle("odd", "even", name: "row_class") -%>">
419
+ # <td>
420
+ # <% item.values.each do |value| %>
421
+ # <%# Create a named cycle "colors" %>
422
+ # <span style="color:<%= cycle("red", "green", "blue", name: "colors") -%>">
423
+ # <%= value %>
424
+ # </span>
425
+ # <% end %>
426
+ # <% reset_cycle("colors") %>
427
+ # </td>
428
+ # </tr>
429
+ # <% end %>
430
+ def cycle(first_value, *values)
431
+ options = values.extract_options!
432
+ name = options.fetch(:name, "default")
433
+
434
+ values.unshift(*first_value)
435
+
436
+ cycle = get_cycle(name)
437
+ unless cycle && cycle.values == values
438
+ cycle = set_cycle(name, Cycle.new(*values))
439
+ end
440
+ cycle.to_s
441
+ end
442
+
443
+ # Returns the current cycle string after a cycle has been started. Useful
444
+ # for complex table highlighting or any other design need which requires
445
+ # the current cycle string in more than one place.
446
+ #
447
+ # <%# Alternate background colors %>
448
+ # <% @items = [1,2,3,4] %>
449
+ # <% @items.each do |item| %>
450
+ # <div style="background-color:<%= cycle("red","white","blue") %>">
451
+ # <span style="background-color:<%= current_cycle %>"><%= item %></span>
452
+ # </div>
453
+ # <% end %>
454
+ def current_cycle(name = "default")
455
+ cycle = get_cycle(name)
456
+ cycle.current_value if cycle
457
+ end
458
+
459
+ # Resets a cycle so that it starts from the first element the next time
460
+ # it is called. Pass in +name+ to reset a named cycle.
461
+ #
462
+ # <%# Alternate CSS classes for even and odd numbers... %>
463
+ # <% @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]] %>
464
+ # <table>
465
+ # <% @items.each do |item| %>
466
+ # <tr class="<%= cycle("even", "odd") -%>">
467
+ # <% item.each do |value| %>
468
+ # <span style="color:<%= cycle("#333", "#666", "#999", name: "colors") -%>">
469
+ # <%= value %>
470
+ # </span>
471
+ # <% end %>
472
+ #
473
+ # <% reset_cycle("colors") %>
474
+ # </tr>
475
+ # <% end %>
476
+ # </table>
477
+ def reset_cycle(name = "default")
478
+ cycle = get_cycle(name)
479
+ cycle.reset if cycle
480
+ end
481
+
482
+ class Cycle # :nodoc:
483
+ attr_reader :values
484
+
485
+ def initialize(first_value, *values)
486
+ @values = values.unshift(first_value)
487
+ reset
488
+ end
489
+
490
+ def reset
491
+ @index = 0
492
+ end
493
+
494
+ def current_value
495
+ @values[previous_index].to_s
496
+ end
497
+
498
+ def to_s
499
+ value = @values[@index].to_s
500
+ @index = next_index
501
+ value
502
+ end
503
+
504
+ private
505
+ def next_index
506
+ step_index(1)
507
+ end
508
+
509
+ def previous_index
510
+ step_index(-1)
511
+ end
512
+
513
+ def step_index(n)
514
+ (@index + n) % @values.size
515
+ end
516
+ end
517
+
518
+ private
519
+ # The cycle helpers need to store the cycles in a place that is
520
+ # guaranteed to be reset every time a page is rendered, so it
521
+ # uses an instance variable of ActionView::Base.
522
+ def get_cycle(name)
523
+ @_cycles = Hash.new unless defined?(@_cycles)
524
+ @_cycles[name]
525
+ end
526
+
527
+ def set_cycle(name, cycle_object)
528
+ @_cycles = Hash.new unless defined?(@_cycles)
529
+ @_cycles[name] = cycle_object
530
+ end
531
+
532
+ def split_paragraphs(text)
533
+ return [] if text.blank?
534
+
535
+ text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t|
536
+ t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t
537
+ end
538
+ end
539
+
540
+ def cut_excerpt_part(part_position, part, separator, options)
541
+ return "", "" unless part
542
+
543
+ radius = options.fetch(:radius, 100)
544
+ omission = options.fetch(:omission, "...")
545
+
546
+ if separator != ""
547
+ part = part.split(separator)
548
+ part.delete("")
549
+ end
550
+
551
+ affix = part.length > radius ? omission : ""
552
+
553
+ part =
554
+ if part_position == :first
555
+ part.last(radius)
556
+ else
557
+ part.first(radius)
558
+ end
559
+
560
+ if separator != ""
561
+ part = part.join(separator)
562
+ end
563
+
564
+ return affix, part
565
+ end
566
+ end
567
+ end
568
+ end