actionview 6.1.7.2 → 7.1.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +299 -277
- data/MIT-LICENSE +2 -1
- data/README.rdoc +3 -3
- data/app/assets/javascripts/rails-ujs.esm.js +686 -0
- data/app/assets/javascripts/rails-ujs.js +630 -0
- data/lib/action_view/base.rb +37 -19
- data/lib/action_view/buffers.rb +107 -9
- data/lib/action_view/cache_expiry.rb +48 -37
- data/lib/action_view/context.rb +1 -1
- data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
- data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
- data/lib/action_view/dependency_tracker.rb +6 -147
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +8 -5
- data/lib/action_view/flows.rb +4 -4
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +200 -60
- data/lib/action_view/helpers/asset_url_helper.rb +22 -21
- data/lib/action_view/helpers/atom_feed_helper.rb +8 -9
- data/lib/action_view/helpers/cache_helper.rb +55 -12
- data/lib/action_view/helpers/capture_helper.rb +34 -14
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +8 -2
- data/lib/action_view/helpers/csp_helper.rb +3 -3
- data/lib/action_view/helpers/csrf_helper.rb +4 -4
- data/lib/action_view/helpers/date_helper.rb +123 -57
- data/lib/action_view/helpers/debug_helper.rb +6 -4
- data/lib/action_view/helpers/form_helper.rb +253 -97
- data/lib/action_view/helpers/form_options_helper.rb +72 -34
- data/lib/action_view/helpers/form_tag_helper.rb +189 -58
- data/lib/action_view/helpers/javascript_helper.rb +4 -5
- data/lib/action_view/helpers/number_helper.rb +43 -335
- data/lib/action_view/helpers/output_safety_helper.rb +6 -6
- data/lib/action_view/helpers/rendering_helper.rb +6 -7
- data/lib/action_view/helpers/sanitize_helper.rb +54 -24
- data/lib/action_view/helpers/tag_helper.rb +42 -35
- data/lib/action_view/helpers/tags/base.rb +16 -77
- data/lib/action_view/helpers/tags/check_box.rb +1 -1
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
- data/lib/action_view/helpers/tags/collection_select.rb +4 -1
- data/lib/action_view/helpers/tags/date_field.rb +1 -1
- data/lib/action_view/helpers/tags/date_select.rb +2 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
- data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
- data/lib/action_view/helpers/tags/file_field.rb +16 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/month_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +4 -1
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/time_field.rb +11 -2
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
- data/lib/action_view/helpers/tags.rb +5 -2
- data/lib/action_view/helpers/text_helper.rb +180 -97
- data/lib/action_view/helpers/translation_helper.rb +14 -45
- data/lib/action_view/helpers/url_helper.rb +230 -132
- data/lib/action_view/helpers.rb +27 -25
- data/lib/action_view/layouts.rb +15 -10
- data/lib/action_view/log_subscriber.rb +49 -32
- data/lib/action_view/lookup_context.rb +58 -61
- data/lib/action_view/model_naming.rb +2 -2
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +28 -35
- data/lib/action_view/railtie.rb +44 -9
- data/lib/action_view/record_identifier.rb +16 -9
- data/lib/action_view/render_parser.rb +188 -0
- data/lib/action_view/renderer/abstract_renderer.rb +3 -3
- data/lib/action_view/renderer/collection_renderer.rb +10 -2
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +21 -3
- data/lib/action_view/renderer/partial_renderer.rb +3 -36
- data/lib/action_view/renderer/renderer.rb +6 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +6 -5
- data/lib/action_view/renderer/template_renderer.rb +9 -4
- data/lib/action_view/rendering.rb +25 -7
- data/lib/action_view/ripper_ast_parser.rb +198 -0
- data/lib/action_view/routing_url_for.rb +8 -5
- data/lib/action_view/template/error.rb +122 -14
- data/lib/action_view/template/handlers/builder.rb +4 -4
- data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
- data/lib/action_view/template/handlers/erb.rb +79 -1
- data/lib/action_view/template/handlers.rb +4 -4
- data/lib/action_view/template/html.rb +4 -4
- data/lib/action_view/template/inline.rb +3 -3
- data/lib/action_view/template/raw_file.rb +4 -4
- data/lib/action_view/template/renderable.rb +1 -1
- data/lib/action_view/template/resolver.rb +96 -313
- data/lib/action_view/template/text.rb +4 -4
- data/lib/action_view/template/types.rb +25 -32
- data/lib/action_view/template.rb +245 -41
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +66 -0
- data/lib/action_view/test_case.rb +182 -23
- data/lib/action_view/testing/resolvers.rb +11 -12
- data/lib/action_view/unbound_template.rb +43 -7
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +19 -28
- data/lib/action_view.rb +6 -4
- data/lib/assets/compiled/rails-ujs.js +36 -5
- metadata +32 -25
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView
|
|
4
|
+
module Helpers
|
|
5
|
+
module Tags # :nodoc:
|
|
6
|
+
module SelectRenderer # :nodoc:
|
|
7
|
+
private
|
|
8
|
+
def select_content_tag(option_tags, options, html_options)
|
|
9
|
+
html_options = html_options.stringify_keys
|
|
10
|
+
[:required, :multiple, :size].each do |prop|
|
|
11
|
+
html_options[prop.to_s] = options.delete(prop) if options.key?(prop) && !html_options.key?(prop.to_s)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
add_default_name_and_id(html_options)
|
|
15
|
+
|
|
16
|
+
if placeholder_required?(html_options)
|
|
17
|
+
raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
|
|
18
|
+
options[:include_blank] ||= true unless options[:prompt]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
value = options.fetch(:selected) { value() }
|
|
22
|
+
select = content_tag("select", add_options(option_tags, options, value), html_options)
|
|
23
|
+
|
|
24
|
+
if html_options["multiple"] && options.fetch(:include_hidden, true)
|
|
25
|
+
tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "", autocomplete: "off") + select
|
|
26
|
+
else
|
|
27
|
+
select
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def placeholder_required?(html_options)
|
|
32
|
+
# See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required
|
|
33
|
+
html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def add_options(option_tags, options, value = nil)
|
|
37
|
+
if options[:include_blank]
|
|
38
|
+
content = (options[:include_blank] if options[:include_blank].is_a?(String))
|
|
39
|
+
label = (" " unless content)
|
|
40
|
+
option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if value.blank? && options[:prompt]
|
|
44
|
+
tag_options = { value: "" }.tap do |prompt_opts|
|
|
45
|
+
prompt_opts[:disabled] = true if options[:disabled] == ""
|
|
46
|
+
prompt_opts[:selected] = true if options[:selected] == ""
|
|
47
|
+
end
|
|
48
|
+
option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
option_tags
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -4,9 +4,18 @@ module ActionView
|
|
|
4
4
|
module Helpers
|
|
5
5
|
module Tags # :nodoc:
|
|
6
6
|
class TimeField < DatetimeField # :nodoc:
|
|
7
|
+
def initialize(object_name, method_name, template_object, options = {})
|
|
8
|
+
@include_seconds = options.delete(:include_seconds) { true }
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
7
12
|
private
|
|
8
|
-
def
|
|
9
|
-
|
|
13
|
+
def format_datetime(value)
|
|
14
|
+
if @include_seconds
|
|
15
|
+
value&.strftime("%T.%L")
|
|
16
|
+
else
|
|
17
|
+
value&.strftime("%H:%M")
|
|
18
|
+
end
|
|
10
19
|
end
|
|
11
20
|
end
|
|
12
21
|
end
|
|
@@ -4,6 +4,9 @@ module ActionView
|
|
|
4
4
|
module Helpers
|
|
5
5
|
module Tags # :nodoc:
|
|
6
6
|
class TimeZoneSelect < Base # :nodoc:
|
|
7
|
+
include SelectRenderer
|
|
8
|
+
include FormOptionsHelper
|
|
9
|
+
|
|
7
10
|
def initialize(object_name, method_name, template_object, priority_zones, options, html_options)
|
|
8
11
|
@priority_zones = priority_zones
|
|
9
12
|
@html_options = html_options
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView
|
|
4
|
+
module Helpers
|
|
5
|
+
module Tags # :nodoc:
|
|
6
|
+
class WeekdaySelect < Base # :nodoc:
|
|
7
|
+
include SelectRenderer
|
|
8
|
+
include FormOptionsHelper
|
|
9
|
+
|
|
10
|
+
def initialize(object_name, method_name, template_object, options, html_options)
|
|
11
|
+
@html_options = html_options
|
|
12
|
+
|
|
13
|
+
super(object_name, method_name, template_object, options)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def render
|
|
17
|
+
select_content_tag(
|
|
18
|
+
weekday_options_for_select(
|
|
19
|
+
value || @options[:selected],
|
|
20
|
+
index_as_value: @options.fetch(:index_as_value, false),
|
|
21
|
+
day_format: @options.fetch(:day_format, :day_names),
|
|
22
|
+
beginning_of_week: @options.fetch(:beginning_of_week, Date.beginning_of_week)
|
|
23
|
+
),
|
|
24
|
+
@options,
|
|
25
|
+
@html_options
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ActionView
|
|
4
|
-
module Helpers
|
|
5
|
-
module Tags
|
|
4
|
+
module Helpers # :nodoc:
|
|
5
|
+
module Tags # :nodoc:
|
|
6
6
|
extend ActiveSupport::Autoload
|
|
7
7
|
|
|
8
|
+
autoload :SelectRenderer
|
|
9
|
+
|
|
8
10
|
eager_autoload do
|
|
9
11
|
autoload :Base
|
|
10
12
|
autoload :Translator
|
|
@@ -38,6 +40,7 @@ module ActionView
|
|
|
38
40
|
autoload :TimeZoneSelect
|
|
39
41
|
autoload :UrlField
|
|
40
42
|
autoload :WeekField
|
|
43
|
+
autoload :WeekdaySelect
|
|
41
44
|
end
|
|
42
45
|
end
|
|
43
46
|
end
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_support/core_ext/string/filters"
|
|
4
|
+
require "active_support/core_ext/string/access"
|
|
4
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"
|
|
5
9
|
|
|
6
10
|
module ActionView
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
module Helpers # :nodoc:
|
|
12
|
+
# = Action View Text \Helpers
|
|
13
|
+
#
|
|
9
14
|
# The TextHelper module provides a set of methods for filtering, formatting
|
|
10
15
|
# and transforming strings, which can reduce the amount of inline Ruby code in
|
|
11
16
|
# your views. These helper methods extend Action View making them callable
|
|
@@ -36,21 +41,25 @@ module ActionView
|
|
|
36
41
|
include OutputSafetyHelper
|
|
37
42
|
|
|
38
43
|
# The preferred method of outputting text in your views is to use the
|
|
39
|
-
#
|
|
44
|
+
# <tt><%= "text" %></tt> eRuby syntax. The regular +puts+ and +print+ methods
|
|
40
45
|
# do not operate as expected in an eRuby code block. If you absolutely must
|
|
41
|
-
# output text within a non-output code block (i.e.,
|
|
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" %>
|
|
42
50
|
#
|
|
43
51
|
# <%
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# if logged_in
|
|
48
|
-
# concat "Logged in!"
|
|
49
|
-
# else
|
|
50
|
-
# concat link_to('login', action: :login)
|
|
51
|
-
# end
|
|
52
|
-
# # will either display "Logged in!" or a login link
|
|
52
|
+
# unless signed_in?
|
|
53
|
+
# concat link_to("Sign In", action: :sign_in)
|
|
54
|
+
# end
|
|
53
55
|
# %>
|
|
56
|
+
#
|
|
57
|
+
# is equivalent to
|
|
58
|
+
#
|
|
59
|
+
# <% unless signed_in? %>
|
|
60
|
+
# <%= link_to "Sign In", action: :sign_in %>
|
|
61
|
+
# <% end %>
|
|
62
|
+
#
|
|
54
63
|
def concat(string)
|
|
55
64
|
output_buffer << string
|
|
56
65
|
end
|
|
@@ -59,17 +68,36 @@ module ActionView
|
|
|
59
68
|
output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string)
|
|
60
69
|
end
|
|
61
70
|
|
|
62
|
-
# Truncates
|
|
63
|
-
#
|
|
64
|
-
#
|
|
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.
|
|
65
89
|
#
|
|
66
|
-
#
|
|
90
|
+
# [+:omission+]
|
|
91
|
+
# The string to append after truncating. Defaults to <tt>"..."</tt>.
|
|
67
92
|
#
|
|
68
|
-
#
|
|
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+.
|
|
69
96
|
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
97
|
+
# [+:escape+]
|
|
98
|
+
# Whether to escape the result. Defaults to true.
|
|
99
|
+
#
|
|
100
|
+
# ==== Examples
|
|
73
101
|
#
|
|
74
102
|
# truncate("Once upon a time in a world far far away")
|
|
75
103
|
# # => "Once upon a time in a world..."
|
|
@@ -90,7 +118,7 @@ module ActionView
|
|
|
90
118
|
# # => "<p>Once upon a time in a wo..."
|
|
91
119
|
#
|
|
92
120
|
# truncate("Once upon a time in a world far far away") { link_to "Continue", "#" }
|
|
93
|
-
# # => "Once upon a time in a
|
|
121
|
+
# # => "Once upon a time in a world...<a href=\"#\">Continue</a>"
|
|
94
122
|
def truncate(text, options = {}, &block)
|
|
95
123
|
if text
|
|
96
124
|
length = options.fetch(:length, 30)
|
|
@@ -102,76 +130,108 @@ module ActionView
|
|
|
102
130
|
end
|
|
103
131
|
end
|
|
104
132
|
|
|
105
|
-
# Highlights
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
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
|
|
111
153
|
#
|
|
112
154
|
# highlight('You searched for: rails', 'rails')
|
|
113
|
-
# # => You searched for: <mark>rails</mark>
|
|
155
|
+
# # => "You searched for: <mark>rails</mark>"
|
|
114
156
|
#
|
|
115
157
|
# highlight('You searched for: rails', /for|rails/)
|
|
116
|
-
# # => You searched <mark>for</mark>: <mark>rails</mark>
|
|
158
|
+
# # => "You searched <mark>for</mark>: <mark>rails</mark>"
|
|
117
159
|
#
|
|
118
160
|
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
|
|
119
|
-
# # => You searched for: ruby, rails, dhh
|
|
161
|
+
# # => "You searched for: ruby, rails, dhh"
|
|
120
162
|
#
|
|
121
163
|
# highlight('You searched for: rails', ['for', 'rails'], highlighter: '<em>\1</em>')
|
|
122
|
-
# # => You searched <em>for</em>: <em>rails</em>
|
|
164
|
+
# # => "You searched <em>for</em>: <em>rails</em>"
|
|
123
165
|
#
|
|
124
166
|
# highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
|
|
125
|
-
# # => You searched for: <a href
|
|
167
|
+
# # => "You searched for: <a href=\"search?q=rails\">rails</a>"
|
|
126
168
|
#
|
|
127
169
|
# highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
|
|
128
|
-
# # => You searched for: <a href
|
|
170
|
+
# # => "You searched for: <a href=\"search?q=rails\">rails</a>"
|
|
129
171
|
#
|
|
130
172
|
# highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
|
|
131
|
-
# # => <a href
|
|
132
|
-
def highlight(text, phrases, options = {})
|
|
173
|
+
# # => "<a href=\"javascript:alert('no!')\">ruby</a> on <mark>rails</mark>"
|
|
174
|
+
def highlight(text, phrases, options = {}, &block)
|
|
133
175
|
text = sanitize(text) if options.fetch(:sanitize, true)
|
|
134
176
|
|
|
135
177
|
if text.blank? || phrases.blank?
|
|
136
178
|
text || ""
|
|
137
179
|
else
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
148
193
|
end.html_safe
|
|
149
194
|
end
|
|
150
195
|
|
|
151
|
-
# Extracts
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
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
|
|
157
217
|
#
|
|
158
218
|
# excerpt('This is an example', 'an', radius: 5)
|
|
159
|
-
# # => ...s is an exam...
|
|
219
|
+
# # => "...s is an exam..."
|
|
160
220
|
#
|
|
161
221
|
# excerpt('This is an example', 'is', radius: 5)
|
|
162
|
-
# # => This is a...
|
|
222
|
+
# # => "This is a..."
|
|
163
223
|
#
|
|
164
224
|
# excerpt('This is an example', 'is')
|
|
165
|
-
# # => This is an example
|
|
225
|
+
# # => "This is an example"
|
|
166
226
|
#
|
|
167
227
|
# excerpt('This next thing is an example', 'ex', radius: 2)
|
|
168
|
-
# # => ...next...
|
|
228
|
+
# # => "...next..."
|
|
169
229
|
#
|
|
170
230
|
# excerpt('This is also an example', 'an', radius: 8, omission: '<chop> ')
|
|
171
|
-
# # => <chop> is also an example
|
|
231
|
+
# # => "<chop> is also an example"
|
|
172
232
|
#
|
|
173
233
|
# excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1)
|
|
174
|
-
# # => ...a very beautiful...
|
|
234
|
+
# # => "...a very beautiful..."
|
|
175
235
|
def excerpt(text, phrase, options = {})
|
|
176
236
|
return unless text && phrase
|
|
177
237
|
|
|
@@ -207,26 +267,26 @@ module ActionView
|
|
|
207
267
|
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
|
|
208
268
|
# +plural+ is supplied, it will use that when count is > 1, otherwise
|
|
209
269
|
# it will use the Inflector to determine the plural form for the given locale,
|
|
210
|
-
# which defaults to I18n.locale
|
|
270
|
+
# which defaults to +I18n.locale+.
|
|
211
271
|
#
|
|
212
272
|
# The word will be pluralized using rules defined for the locale
|
|
213
273
|
# (you must define your own inflection rules for languages other than English).
|
|
214
274
|
# See ActiveSupport::Inflector.pluralize
|
|
215
275
|
#
|
|
216
276
|
# pluralize(1, 'person')
|
|
217
|
-
# # => 1 person
|
|
277
|
+
# # => "1 person"
|
|
218
278
|
#
|
|
219
279
|
# pluralize(2, 'person')
|
|
220
|
-
# # => 2 people
|
|
280
|
+
# # => "2 people"
|
|
221
281
|
#
|
|
222
282
|
# pluralize(3, 'person', plural: 'users')
|
|
223
|
-
# # => 3 users
|
|
283
|
+
# # => "3 users"
|
|
224
284
|
#
|
|
225
285
|
# pluralize(0, 'person')
|
|
226
|
-
# # => 0 people
|
|
286
|
+
# # => "0 people"
|
|
227
287
|
#
|
|
228
288
|
# pluralize(2, 'Person', locale: :de)
|
|
229
|
-
# # => 2 Personen
|
|
289
|
+
# # => "2 Personen"
|
|
230
290
|
def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
|
|
231
291
|
word = if count == 1 || count.to_s.match?(/^1(\.0+)?$/)
|
|
232
292
|
singular
|
|
@@ -242,29 +302,39 @@ module ActionView
|
|
|
242
302
|
# (which is 80 by default).
|
|
243
303
|
#
|
|
244
304
|
# word_wrap('Once upon a time')
|
|
245
|
-
# # => Once upon a time
|
|
305
|
+
# # => "Once upon a time"
|
|
246
306
|
#
|
|
247
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...')
|
|
248
|
-
# # => 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...
|
|
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..."
|
|
249
309
|
#
|
|
250
310
|
# word_wrap('Once upon a time', line_width: 8)
|
|
251
|
-
# # => Once\nupon a\ntime
|
|
311
|
+
# # => "Once\nupon a\ntime"
|
|
252
312
|
#
|
|
253
313
|
# word_wrap('Once upon a time', line_width: 1)
|
|
254
|
-
# # => Once\nupon\na\ntime
|
|
314
|
+
# # => "Once\nupon\na\ntime"
|
|
255
315
|
#
|
|
256
|
-
#
|
|
316
|
+
# You can also specify a custom +break_sequence+ ("\n" by default):
|
|
257
317
|
#
|
|
258
318
|
# word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n")
|
|
259
|
-
# # => Once\r\nupon\r\na\r\ntime
|
|
319
|
+
# # => "Once\r\nupon\r\na\r\ntime"
|
|
260
320
|
def word_wrap(text, line_width: 80, break_sequence: "\n")
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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)
|
|
264
334
|
end
|
|
265
335
|
|
|
266
336
|
# Returns +text+ transformed into HTML using simple formatting rules.
|
|
267
|
-
# Two or more consecutive newlines(<tt>\n\n</tt> or <tt>\r\n\r\n</tt>) are
|
|
337
|
+
# Two or more consecutive newlines (<tt>\n\n</tt> or <tt>\r\n\r\n</tt>) are
|
|
268
338
|
# considered a paragraph and wrapped in <tt><p></tt> tags. One newline
|
|
269
339
|
# (<tt>\n</tt> or <tt>\r\n</tt>) is considered a linebreak and a
|
|
270
340
|
# <tt><br /></tt> tag is appended. This method does not remove the
|
|
@@ -275,6 +345,7 @@ module ActionView
|
|
|
275
345
|
#
|
|
276
346
|
# ==== Options
|
|
277
347
|
# * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
|
|
348
|
+
# * <tt>:sanitize_options</tt> - Any extra options you want appended to the sanitize.
|
|
278
349
|
# * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt>
|
|
279
350
|
#
|
|
280
351
|
# ==== Examples
|
|
@@ -299,10 +370,13 @@ module ActionView
|
|
|
299
370
|
#
|
|
300
371
|
# simple_format("<blink>Blinkable!</blink> It's true.", {}, sanitize: false)
|
|
301
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>"
|
|
302
376
|
def simple_format(text, html_options = {}, options = {})
|
|
303
|
-
wrapper_tag = options
|
|
377
|
+
wrapper_tag = options[:wrapper_tag] || "p"
|
|
304
378
|
|
|
305
|
-
text = sanitize(text) if options.fetch(:sanitize, true)
|
|
379
|
+
text = sanitize(text, options.fetch(:sanitize_options, {})) if options.fetch(:sanitize, true)
|
|
306
380
|
paragraphs = split_paragraphs(text)
|
|
307
381
|
|
|
308
382
|
if paragraphs.empty?
|
|
@@ -314,7 +388,7 @@ module ActionView
|
|
|
314
388
|
end
|
|
315
389
|
end
|
|
316
390
|
|
|
317
|
-
# Creates a Cycle object whose
|
|
391
|
+
# Creates a Cycle object whose +to_s+ method cycles through elements of an
|
|
318
392
|
# array every time it is called. This can be used for example, to alternate
|
|
319
393
|
# classes for table rows. You can use named cycles to allow nesting in loops.
|
|
320
394
|
# Passing a Hash as the last parameter with a <tt>:name</tt> key will create a
|
|
@@ -323,8 +397,8 @@ module ActionView
|
|
|
323
397
|
# and passing the name of the cycle. The current cycle string can be obtained
|
|
324
398
|
# anytime using the current_cycle method.
|
|
325
399
|
#
|
|
326
|
-
#
|
|
327
|
-
# @items = [1,2,3,4]
|
|
400
|
+
# <%# Alternate CSS classes for even and odd numbers... %>
|
|
401
|
+
# <% @items = [1,2,3,4] %>
|
|
328
402
|
# <table>
|
|
329
403
|
# <% @items.each do |item| %>
|
|
330
404
|
# <tr class="<%= cycle("odd", "even") -%>">
|
|
@@ -334,10 +408,12 @@ module ActionView
|
|
|
334
408
|
# </table>
|
|
335
409
|
#
|
|
336
410
|
#
|
|
337
|
-
#
|
|
338
|
-
# @items =
|
|
339
|
-
#
|
|
340
|
-
#
|
|
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
|
+
# ] %>
|
|
341
417
|
# <% @items.each do |item| %>
|
|
342
418
|
# <tr class="<%= cycle("odd", "even", name: "row_class") -%>">
|
|
343
419
|
# <td>
|
|
@@ -368,8 +444,8 @@ module ActionView
|
|
|
368
444
|
# for complex table highlighting or any other design need which requires
|
|
369
445
|
# the current cycle string in more than one place.
|
|
370
446
|
#
|
|
371
|
-
#
|
|
372
|
-
# @items = [1,2,3,4]
|
|
447
|
+
# <%# Alternate background colors %>
|
|
448
|
+
# <% @items = [1,2,3,4] %>
|
|
373
449
|
# <% @items.each do |item| %>
|
|
374
450
|
# <div style="background-color:<%= cycle("red","white","blue") %>">
|
|
375
451
|
# <span style="background-color:<%= current_cycle %>"><%= item %></span>
|
|
@@ -383,8 +459,8 @@ module ActionView
|
|
|
383
459
|
# Resets a cycle so that it starts from the first element the next time
|
|
384
460
|
# it is called. Pass in +name+ to reset a named cycle.
|
|
385
461
|
#
|
|
386
|
-
#
|
|
387
|
-
# @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]]
|
|
462
|
+
# <%# Alternate CSS classes for even and odd numbers... %>
|
|
463
|
+
# <% @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]] %>
|
|
388
464
|
# <table>
|
|
389
465
|
# <% @items.each do |item| %>
|
|
390
466
|
# <tr class="<%= cycle("even", "odd") -%>">
|
|
@@ -403,7 +479,7 @@ module ActionView
|
|
|
403
479
|
cycle.reset if cycle
|
|
404
480
|
end
|
|
405
481
|
|
|
406
|
-
class Cycle
|
|
482
|
+
class Cycle # :nodoc:
|
|
407
483
|
attr_reader :values
|
|
408
484
|
|
|
409
485
|
def initialize(first_value, *values)
|
|
@@ -467,18 +543,25 @@ module ActionView
|
|
|
467
543
|
radius = options.fetch(:radius, 100)
|
|
468
544
|
omission = options.fetch(:omission, "...")
|
|
469
545
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
|
473
559
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
part.drop(drop_index)
|
|
477
|
-
else
|
|
478
|
-
part.first(radius)
|
|
560
|
+
if separator != ""
|
|
561
|
+
part = part.join(separator)
|
|
479
562
|
end
|
|
480
563
|
|
|
481
|
-
return affix, part
|
|
564
|
+
return affix, part
|
|
482
565
|
end
|
|
483
566
|
end
|
|
484
567
|
end
|