actionview 6.0.4.6 → 6.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +152 -288
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/action_view/base.rb +21 -52
- data/lib/action_view/cache_expiry.rb +1 -2
- data/lib/action_view/context.rb +0 -1
- data/lib/action_view/dependency_tracker.rb +10 -4
- data/lib/action_view/digestor.rb +3 -2
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +40 -15
- data/lib/action_view/helpers/asset_url_helper.rb +6 -4
- data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
- data/lib/action_view/helpers/cache_helper.rb +10 -16
- data/lib/action_view/helpers/date_helper.rb +4 -4
- data/lib/action_view/helpers/form_helper.rb +59 -17
- data/lib/action_view/helpers/form_options_helper.rb +7 -16
- data/lib/action_view/helpers/form_tag_helper.rb +8 -6
- data/lib/action_view/helpers/javascript_helper.rb +3 -3
- data/lib/action_view/helpers/number_helper.rb +6 -6
- data/lib/action_view/helpers/rendering_helper.rb +11 -3
- data/lib/action_view/helpers/sanitize_helper.rb +2 -2
- data/lib/action_view/helpers/tag_helper.rb +92 -17
- data/lib/action_view/helpers/tags/base.rb +9 -5
- data/lib/action_view/helpers/tags/date_field.rb +1 -1
- data/lib/action_view/helpers/tags/date_select.rb +2 -2
- data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -1
- data/lib/action_view/helpers/tags/label.rb +4 -0
- data/lib/action_view/helpers/tags/month_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +1 -1
- data/lib/action_view/helpers/tags/time_field.rb +1 -1
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/text_helper.rb +1 -1
- data/lib/action_view/helpers/translation_helper.rb +88 -53
- data/lib/action_view/helpers/url_helper.rb +107 -13
- data/lib/action_view/layouts.rb +3 -2
- data/lib/action_view/log_subscriber.rb +26 -10
- data/lib/action_view/lookup_context.rb +3 -18
- data/lib/action_view/path_set.rb +0 -3
- data/lib/action_view/railtie.rb +35 -46
- data/lib/action_view/renderer/abstract_renderer.rb +93 -14
- data/lib/action_view/renderer/collection_renderer.rb +192 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +25 -26
- data/lib/action_view/renderer/partial_renderer.rb +20 -282
- data/lib/action_view/renderer/renderer.rb +44 -1
- data/lib/action_view/renderer/streaming_template_renderer.rb +5 -1
- data/lib/action_view/renderer/template_renderer.rb +15 -12
- data/lib/action_view/rendering.rb +3 -1
- data/lib/action_view/routing_url_for.rb +1 -1
- data/lib/action_view/template/handlers/erb/erubi.rb +9 -7
- data/lib/action_view/template/handlers/erb.rb +10 -14
- data/lib/action_view/template/handlers.rb +0 -26
- data/lib/action_view/template/html.rb +1 -11
- data/lib/action_view/template/raw_file.rb +0 -3
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +82 -40
- data/lib/action_view/template/text.rb +0 -3
- data/lib/action_view/template.rb +9 -49
- data/lib/action_view/test_case.rb +18 -25
- data/lib/action_view/testing/resolvers.rb +10 -31
- data/lib/action_view/unbound_template.rb +3 -3
- data/lib/action_view/view_paths.rb +34 -36
- data/lib/action_view.rb +4 -1
- data/lib/assets/compiled/rails-ujs.js +1 -1
- metadata +20 -17
@@ -11,6 +11,7 @@ require "active_support/core_ext/module/attribute_accessors"
|
|
11
11
|
require "active_support/core_ext/hash/slice"
|
12
12
|
require "active_support/core_ext/string/output_safety"
|
13
13
|
require "active_support/core_ext/string/inflections"
|
14
|
+
require "active_support/core_ext/symbol/starts_ends_with"
|
14
15
|
|
15
16
|
module ActionView
|
16
17
|
# = Action View Form Helpers
|
@@ -888,7 +889,7 @@ module ActionView
|
|
888
889
|
#
|
889
890
|
# Now, when you use a form element with the <tt>_destroy</tt> parameter,
|
890
891
|
# with a value that evaluates to +true+, you will destroy the associated
|
891
|
-
# model (
|
892
|
+
# model (e.g. 1, '1', true, or 'true'):
|
892
893
|
#
|
893
894
|
# <%= form_for @person do |person_form| %>
|
894
895
|
# ...
|
@@ -977,7 +978,7 @@ module ActionView
|
|
977
978
|
# This will allow you to specify which models to destroy in the
|
978
979
|
# attributes hash by adding a form element for the <tt>_destroy</tt>
|
979
980
|
# parameter with a value that evaluates to +true+
|
980
|
-
# (
|
981
|
+
# (e.g. 1, '1', true, or 'true'):
|
981
982
|
#
|
982
983
|
# <%= form_for @person do |person_form| %>
|
983
984
|
# ...
|
@@ -1110,6 +1111,16 @@ module ActionView
|
|
1110
1111
|
# label(:post, :privacy, "Public Post", value: "public")
|
1111
1112
|
# # => <label for="post_privacy_public">Public Post</label>
|
1112
1113
|
#
|
1114
|
+
# label(:post, :cost) do |translation|
|
1115
|
+
# content_tag(:span, translation, class: "cost_label")
|
1116
|
+
# end
|
1117
|
+
# # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
|
1118
|
+
#
|
1119
|
+
# label(:post, :cost) do |builder|
|
1120
|
+
# content_tag(:span, builder.translation, class: "cost_label")
|
1121
|
+
# end
|
1122
|
+
# # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
|
1123
|
+
#
|
1113
1124
|
# label(:post, :terms) do
|
1114
1125
|
# raw('Accept <a href="/terms">Terms</a>.')
|
1115
1126
|
# end
|
@@ -1668,8 +1679,8 @@ module ActionView
|
|
1668
1679
|
|
1669
1680
|
convert_to_legacy_options(@options)
|
1670
1681
|
|
1671
|
-
if @object_name
|
1672
|
-
if (object ||= @template.instance_variable_get("@#{
|
1682
|
+
if @object_name&.end_with?("[]")
|
1683
|
+
if (object ||= @template.instance_variable_get("@#{@object_name[0..-3]}")) && object.respond_to?(:to_param)
|
1673
1684
|
@auto_index = object.to_param
|
1674
1685
|
else
|
1675
1686
|
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
|
@@ -1792,7 +1803,7 @@ module ActionView
|
|
1792
1803
|
# Wraps ActionView::Helpers::FormHelper#time_field for form builders:
|
1793
1804
|
#
|
1794
1805
|
# <%= form_with model: @user do |f| %>
|
1795
|
-
# <%= f.time_field :
|
1806
|
+
# <%= f.time_field :born_at %>
|
1796
1807
|
# <% end %>
|
1797
1808
|
#
|
1798
1809
|
# Please refer to the documentation of the base helper for details.
|
@@ -1904,8 +1915,8 @@ module ActionView
|
|
1904
1915
|
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
|
1905
1916
|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
1906
1917
|
def #{selector}(method, options = {}) # def text_field(method, options = {})
|
1907
|
-
@template.
|
1908
|
-
#{selector.inspect}, #
|
1918
|
+
@template.public_send( # @template.public_send(
|
1919
|
+
#{selector.inspect}, # :text_field,
|
1909
1920
|
@object_name, # @object_name,
|
1910
1921
|
method, # method,
|
1911
1922
|
objectify_options(options)) # objectify_options(options))
|
@@ -2038,7 +2049,7 @@ module ActionView
|
|
2038
2049
|
#
|
2039
2050
|
# Now, when you use a form element with the <tt>_destroy</tt> parameter,
|
2040
2051
|
# with a value that evaluates to +true+, you will destroy the associated
|
2041
|
-
# model (
|
2052
|
+
# model (e.g. 1, '1', true, or 'true'):
|
2042
2053
|
#
|
2043
2054
|
# <%= form_for @person do |person_form| %>
|
2044
2055
|
# ...
|
@@ -2127,7 +2138,7 @@ module ActionView
|
|
2127
2138
|
# This will allow you to specify which models to destroy in the
|
2128
2139
|
# attributes hash by adding a form element for the <tt>_destroy</tt>
|
2129
2140
|
# parameter with a value that evaluates to +true+
|
2130
|
-
# (
|
2141
|
+
# (e.g. 1, '1', true, or 'true'):
|
2131
2142
|
#
|
2132
2143
|
# <%= form_for @person do |person_form| %>
|
2133
2144
|
# ...
|
@@ -2174,15 +2185,14 @@ module ActionView
|
|
2174
2185
|
index = if options.has_key?(:index)
|
2175
2186
|
options[:index]
|
2176
2187
|
elsif defined?(@auto_index)
|
2177
|
-
object_name = object_name.to_s.
|
2188
|
+
object_name = object_name.to_s.delete_suffix("[]")
|
2178
2189
|
@auto_index
|
2179
2190
|
end
|
2180
2191
|
|
2181
2192
|
record_name = if index
|
2182
2193
|
"#{object_name}[#{index}][#{record_name}]"
|
2183
|
-
elsif record_name.
|
2184
|
-
record_name
|
2185
|
-
"#{object_name}#{record_name}"
|
2194
|
+
elsif record_name.end_with?("[]")
|
2195
|
+
"#{object_name}[#{record_name[0..-3]}][#{record_object.id}]"
|
2186
2196
|
else
|
2187
2197
|
"#{object_name}[#{record_name}]"
|
2188
2198
|
end
|
@@ -2245,6 +2255,24 @@ module ActionView
|
|
2245
2255
|
# label(:privacy, "Public Post", value: "public")
|
2246
2256
|
# # => <label for="post_privacy_public">Public Post</label>
|
2247
2257
|
#
|
2258
|
+
# label(:cost) do |translation|
|
2259
|
+
# content_tag(:span, translation, class: "cost_label")
|
2260
|
+
# end
|
2261
|
+
# # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
|
2262
|
+
#
|
2263
|
+
# label(:cost) do |builder|
|
2264
|
+
# content_tag(:span, builder.translation, class: "cost_label")
|
2265
|
+
# end
|
2266
|
+
# # => <label for="post_cost"><span class="cost_label">Total cost</span></label>
|
2267
|
+
#
|
2268
|
+
# label(:cost) do |builder|
|
2269
|
+
# content_tag(:span, builder.translation, class: [
|
2270
|
+
# "cost_label",
|
2271
|
+
# ("error_label" if builder.object.errors.include?(:cost))
|
2272
|
+
# ])
|
2273
|
+
# end
|
2274
|
+
# # => <label for="post_cost"><span class="cost_label error_label">Total cost</span></label>
|
2275
|
+
#
|
2248
2276
|
# label(:terms) do
|
2249
2277
|
# raw('Accept <a href="/terms">Terms</a>.')
|
2250
2278
|
# end
|
@@ -2468,10 +2496,22 @@ module ActionView
|
|
2468
2496
|
# # <strong>Ask me!</strong>
|
2469
2497
|
# # </button>
|
2470
2498
|
#
|
2499
|
+
# button do |text|
|
2500
|
+
# content_tag(:strong, text)
|
2501
|
+
# end
|
2502
|
+
# # => <button name='button' type='submit'>
|
2503
|
+
# # <strong>Create post</strong>
|
2504
|
+
# # </button>
|
2505
|
+
#
|
2471
2506
|
def button(value = nil, options = {}, &block)
|
2472
2507
|
value, options = nil, value if value.is_a?(Hash)
|
2473
2508
|
value ||= submit_default_value
|
2474
|
-
|
2509
|
+
|
2510
|
+
if block_given?
|
2511
|
+
value = @template.capture { yield(value) }
|
2512
|
+
end
|
2513
|
+
|
2514
|
+
@template.button_tag(value, options)
|
2475
2515
|
end
|
2476
2516
|
|
2477
2517
|
def emitted_hidden_id? # :nodoc:
|
@@ -2480,7 +2520,9 @@ module ActionView
|
|
2480
2520
|
|
2481
2521
|
private
|
2482
2522
|
def objectify_options(options)
|
2483
|
-
@default_options.merge(options
|
2523
|
+
result = @default_options.merge(options)
|
2524
|
+
result[:object] = @object
|
2525
|
+
result
|
2484
2526
|
end
|
2485
2527
|
|
2486
2528
|
def submit_default_value
|
@@ -2515,9 +2557,9 @@ module ActionView
|
|
2515
2557
|
association = convert_to_model(association)
|
2516
2558
|
|
2517
2559
|
if association.respond_to?(:persisted?)
|
2518
|
-
association = [association] if @object.
|
2560
|
+
association = [association] if @object.public_send(association_name).respond_to?(:to_ary)
|
2519
2561
|
elsif !association.respond_to?(:to_ary)
|
2520
|
-
association = @object.
|
2562
|
+
association = @object.public_send(association_name)
|
2521
2563
|
end
|
2522
2564
|
|
2523
2565
|
if association.respond_to?(:to_ary)
|
@@ -21,7 +21,7 @@ module ActionView
|
|
21
21
|
# could become:
|
22
22
|
#
|
23
23
|
# <select name="post[category]" id="post_category">
|
24
|
-
# <option value=""></option>
|
24
|
+
# <option value="" label=" "></option>
|
25
25
|
# <option value="joke">joke</option>
|
26
26
|
# <option value="poem">poem</option>
|
27
27
|
# </select>
|
@@ -74,7 +74,6 @@ module ActionView
|
|
74
74
|
# could become:
|
75
75
|
#
|
76
76
|
# <select name="post[category]" id="post_category">
|
77
|
-
# <option value=""></option>
|
78
77
|
# <option value="joke">joke</option>
|
79
78
|
# <option value="poem">poem</option>
|
80
79
|
# <option disabled="disabled" value="restricted">restricted</option>
|
@@ -112,7 +111,7 @@ module ActionView
|
|
112
111
|
# would become:
|
113
112
|
#
|
114
113
|
# <select name="post[person_id]" id="post_person_id">
|
115
|
-
# <option value=""></option>
|
114
|
+
# <option value="" label=" "></option>
|
116
115
|
# <option value="1" selected="selected">David</option>
|
117
116
|
# <option value="2">Eileen</option>
|
118
117
|
# <option value="3">Rafael</option>
|
@@ -143,7 +142,7 @@ module ActionView
|
|
143
142
|
#
|
144
143
|
# The HTML specification says when +multiple+ parameter passed to select and all options got deselected
|
145
144
|
# web browsers do not send any value to server. Unfortunately this introduces a gotcha:
|
146
|
-
# if
|
145
|
+
# if a +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
|
147
146
|
# the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
|
148
147
|
# any mass-assignment idiom like
|
149
148
|
#
|
@@ -569,7 +568,7 @@ module ActionView
|
|
569
568
|
# be obtained in Active Record as a value object). The +model+ parameter
|
570
569
|
# must respond to +all+ and return an array of objects that represent time
|
571
570
|
# zones; each object must respond to +name+. If a Regexp is given it will
|
572
|
-
# attempt to match the zones using
|
571
|
+
# attempt to match the zones using <code>match?</code> method.
|
573
572
|
#
|
574
573
|
# NOTE: Only the option tags are returned, you have to wrap this call in
|
575
574
|
# a regular HTML select tag.
|
@@ -581,7 +580,7 @@ module ActionView
|
|
581
580
|
|
582
581
|
if priority_zones
|
583
582
|
if priority_zones.is_a?(Regexp)
|
584
|
-
priority_zones = zones.select { |z| z
|
583
|
+
priority_zones = zones.select { |z| z.match?(priority_zones) }
|
585
584
|
end
|
586
585
|
|
587
586
|
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
|
@@ -795,7 +794,7 @@ module ActionView
|
|
795
794
|
def extract_values_from_collection(collection, value_method, selected)
|
796
795
|
if selected.is_a?(Proc)
|
797
796
|
collection.map do |element|
|
798
|
-
|
797
|
+
element.public_send(value_method) if selected.call(element)
|
799
798
|
end.compact
|
800
799
|
else
|
801
800
|
selected
|
@@ -803,15 +802,7 @@ module ActionView
|
|
803
802
|
end
|
804
803
|
|
805
804
|
def value_for_collection(item, value)
|
806
|
-
value.respond_to?(:call) ? value.call(item) :
|
807
|
-
end
|
808
|
-
|
809
|
-
def public_or_deprecated_send(item, value)
|
810
|
-
item.public_send(value)
|
811
|
-
rescue NoMethodError
|
812
|
-
raise unless item.respond_to?(value, true) && !item.respond_to?(value)
|
813
|
-
ActiveSupport::Deprecation.warn "Using private methods from view helpers is deprecated (calling private #{item.class}##{value})"
|
814
|
-
item.send(value)
|
805
|
+
value.respond_to?(:call) ? value.call(item) : item.public_send(value)
|
815
806
|
end
|
816
807
|
|
817
808
|
def prompt_text(prompt)
|
@@ -4,6 +4,7 @@ require "cgi"
|
|
4
4
|
require "action_view/helpers/tag_helper"
|
5
5
|
require "active_support/core_ext/string/output_safety"
|
6
6
|
require "active_support/core_ext/module/attribute_accessors"
|
7
|
+
require "active_support/core_ext/symbol/starts_ends_with"
|
7
8
|
|
8
9
|
module ActionView
|
9
10
|
# = Action View Form Tag Helpers
|
@@ -134,7 +135,7 @@ module ActionView
|
|
134
135
|
# # <option selected="selected">MasterCard</option></select>
|
135
136
|
def select_tag(name, option_tags = nil, options = {})
|
136
137
|
option_tags ||= ""
|
137
|
-
html_name = (options[:multiple] == true && !name.
|
138
|
+
html_name = (options[:multiple] == true && !name.end_with?("[]")) ? "#{name}[]" : name
|
138
139
|
|
139
140
|
if options.include?(:include_blank)
|
140
141
|
include_blank = options[:include_blank]
|
@@ -896,15 +897,16 @@ module ActionView
|
|
896
897
|
end
|
897
898
|
|
898
899
|
def set_default_disable_with(value, tag_options)
|
899
|
-
|
900
|
+
return unless ActionView::Base.automatically_disable_submit_tag
|
901
|
+
data = tag_options["data"]
|
900
902
|
|
901
|
-
|
902
|
-
data.delete("disable_with")
|
903
|
-
elsif ActionView::Base.automatically_disable_submit_tag
|
903
|
+
unless tag_options["data-disable-with"] == false || (data && data["disable_with"] == false)
|
904
904
|
disable_with_text = tag_options["data-disable-with"]
|
905
|
-
disable_with_text ||= data["disable_with"]
|
905
|
+
disable_with_text ||= data["disable_with"] if data
|
906
906
|
disable_with_text ||= value.to_s.clone
|
907
907
|
tag_options.deep_merge!("data" => { "disable_with" => disable_with_text })
|
908
|
+
else
|
909
|
+
data.delete("disable_with") if data
|
908
910
|
end
|
909
911
|
|
910
912
|
tag_options.delete("data-disable-with")
|
@@ -51,10 +51,10 @@ module ActionView
|
|
51
51
|
# +html_options+ may be a hash of attributes for the <tt>\<script></tt>
|
52
52
|
# tag.
|
53
53
|
#
|
54
|
-
# javascript_tag "alert('All is good')",
|
54
|
+
# javascript_tag "alert('All is good')", type: 'application/javascript'
|
55
55
|
#
|
56
56
|
# Returns:
|
57
|
-
# <script
|
57
|
+
# <script type="application/javascript">
|
58
58
|
# //<![CDATA[
|
59
59
|
# alert('All is good')
|
60
60
|
# //]]>
|
@@ -63,7 +63,7 @@ module ActionView
|
|
63
63
|
# Instead of passing the content as an argument, you can also use a block
|
64
64
|
# in which case, you pass your +html_options+ as the first parameter.
|
65
65
|
#
|
66
|
-
# <%= javascript_tag
|
66
|
+
# <%= javascript_tag type: 'application/javascript' do -%>
|
67
67
|
# alert('All is good')
|
68
68
|
# <% end -%>
|
69
69
|
#
|
@@ -253,7 +253,7 @@ module ActionView
|
|
253
253
|
end
|
254
254
|
|
255
255
|
# Formats the bytes in +number+ into a more understandable
|
256
|
-
# representation (e.g., giving it 1500 yields 1.
|
256
|
+
# representation (e.g., giving it 1500 yields 1.46 KB). This
|
257
257
|
# method is useful for reporting file sizes to users. You can
|
258
258
|
# customize the format in the +options+ hash.
|
259
259
|
#
|
@@ -299,7 +299,7 @@ module ActionView
|
|
299
299
|
end
|
300
300
|
|
301
301
|
# Pretty prints (formats and approximates) a number in a way it
|
302
|
-
# is more readable by humans (
|
302
|
+
# is more readable by humans (e.g.: 1200000000 becomes "1.2
|
303
303
|
# Billion"). This is useful for numbers that can get very large
|
304
304
|
# (and too hard to read).
|
305
305
|
#
|
@@ -307,7 +307,7 @@ module ActionView
|
|
307
307
|
# size.
|
308
308
|
#
|
309
309
|
# You can also define your own unit-quantifier names if you want
|
310
|
-
# to use other decimal units (
|
310
|
+
# to use other decimal units (e.g.: 1500 becomes "1.5
|
311
311
|
# kilometers", 0.150 becomes "150 milliliters", etc). You may
|
312
312
|
# define a wide range of unit quantifiers, even fractional ones
|
313
313
|
# (centi, deci, mili, etc).
|
@@ -425,9 +425,9 @@ module ActionView
|
|
425
425
|
end
|
426
426
|
|
427
427
|
def escape_units(units)
|
428
|
-
|
429
|
-
|
430
|
-
end
|
428
|
+
units.transform_values do |v|
|
429
|
+
ERB::Util.html_escape(v)
|
430
|
+
end
|
431
431
|
end
|
432
432
|
|
433
433
|
def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
|
@@ -22,8 +22,12 @@ module ActionView
|
|
22
22
|
# type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
|
23
23
|
# object.
|
24
24
|
#
|
25
|
-
# If no options hash is passed or
|
26
|
-
#
|
25
|
+
# If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
|
26
|
+
#
|
27
|
+
# If an object responding to `render_in` is passed, `render_in` is called on the object,
|
28
|
+
# passing in the current view context.
|
29
|
+
#
|
30
|
+
# Otherwise, a partial is rendered using the second parameter as the locals hash.
|
27
31
|
def render(options = {}, locals = {}, &block)
|
28
32
|
case options
|
29
33
|
when Hash
|
@@ -35,7 +39,11 @@ module ActionView
|
|
35
39
|
end
|
36
40
|
end
|
37
41
|
else
|
38
|
-
|
42
|
+
if options.respond_to?(:render_in)
|
43
|
+
options.render_in(self, &block)
|
44
|
+
else
|
45
|
+
view_renderer.render_partial(self, partial: options, locals: locals, &block)
|
46
|
+
end
|
39
47
|
end
|
40
48
|
end
|
41
49
|
|
@@ -129,11 +129,11 @@ module ActionView
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def sanitized_allowed_tags
|
132
|
-
|
132
|
+
safe_list_sanitizer.allowed_tags
|
133
133
|
end
|
134
134
|
|
135
135
|
def sanitized_allowed_attributes
|
136
|
-
|
136
|
+
safe_list_sanitizer.allowed_attributes
|
137
137
|
end
|
138
138
|
|
139
139
|
# Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
|
@@ -13,19 +13,27 @@ module ActionView
|
|
13
13
|
include CaptureHelper
|
14
14
|
include OutputSafetyHelper
|
15
15
|
|
16
|
-
BOOLEAN_ATTRIBUTES = %w(allowfullscreen async autofocus
|
17
|
-
compact controls declare default
|
18
|
-
defaultmuted defaultselected defer
|
19
|
-
enabled formnovalidate hidden indeterminate
|
20
|
-
ismap itemscope loop multiple muted nohref
|
21
|
-
noresize noshade novalidate nowrap open
|
22
|
-
pauseonexit readonly required reversed
|
23
|
-
seamless selected sortable truespeed
|
24
|
-
visible).to_set
|
16
|
+
BOOLEAN_ATTRIBUTES = %w(allowfullscreen allowpaymentrequest async autofocus
|
17
|
+
autoplay checked compact controls declare default
|
18
|
+
defaultchecked defaultmuted defaultselected defer
|
19
|
+
disabled enabled formnovalidate hidden indeterminate
|
20
|
+
inert ismap itemscope loop multiple muted nohref
|
21
|
+
nomodule noresize noshade novalidate nowrap open
|
22
|
+
pauseonexit playsinline readonly required reversed
|
23
|
+
scoped seamless selected sortable truespeed
|
24
|
+
typemustmatch visible).to_set
|
25
25
|
|
26
26
|
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
|
27
|
+
BOOLEAN_ATTRIBUTES.freeze
|
27
28
|
|
28
|
-
|
29
|
+
ARIA_PREFIXES = ["aria", :aria].to_set.freeze
|
30
|
+
DATA_PREFIXES = ["data", :data].to_set.freeze
|
31
|
+
|
32
|
+
TAG_TYPES = {}
|
33
|
+
TAG_TYPES.merge! BOOLEAN_ATTRIBUTES.index_with(:boolean)
|
34
|
+
TAG_TYPES.merge! DATA_PREFIXES.index_with(:data)
|
35
|
+
TAG_TYPES.merge! ARIA_PREFIXES.index_with(:aria)
|
36
|
+
TAG_TYPES.freeze
|
29
37
|
|
30
38
|
PRE_CONTENT_STRINGS = Hash.new { "" }
|
31
39
|
PRE_CONTENT_STRINGS[:textarea] = "\n"
|
@@ -41,6 +49,10 @@ module ActionView
|
|
41
49
|
@view_context = view_context
|
42
50
|
end
|
43
51
|
|
52
|
+
def p(*arguments, **options, &block)
|
53
|
+
tag_string(:p, *arguments, **options, &block)
|
54
|
+
end
|
55
|
+
|
44
56
|
def tag_string(name, content = nil, escape_attributes: true, **options, &block)
|
45
57
|
content = @view_context.capture(self, &block) if block_given?
|
46
58
|
if VOID_ELEMENTS.include?(name) && content.nil?
|
@@ -61,13 +73,31 @@ module ActionView
|
|
61
73
|
output = +""
|
62
74
|
sep = " "
|
63
75
|
options.each_pair do |key, value|
|
64
|
-
|
76
|
+
type = TAG_TYPES[key]
|
77
|
+
if type == :data && value.is_a?(Hash)
|
78
|
+
value.each_pair do |k, v|
|
79
|
+
next if v.nil?
|
80
|
+
output << sep
|
81
|
+
output << prefix_tag_option(key, k, v, escape)
|
82
|
+
end
|
83
|
+
elsif type == :aria && value.is_a?(Hash)
|
65
84
|
value.each_pair do |k, v|
|
66
85
|
next if v.nil?
|
86
|
+
|
87
|
+
case v
|
88
|
+
when Array, Hash
|
89
|
+
tokens = TagHelper.build_tag_values(v)
|
90
|
+
next if tokens.none?
|
91
|
+
|
92
|
+
v = safe_join(tokens, " ")
|
93
|
+
else
|
94
|
+
v = v.to_s
|
95
|
+
end
|
96
|
+
|
67
97
|
output << sep
|
68
98
|
output << prefix_tag_option(key, k, v, escape)
|
69
99
|
end
|
70
|
-
elsif
|
100
|
+
elsif type == :boolean
|
71
101
|
if value
|
72
102
|
output << sep
|
73
103
|
output << boolean_tag_option(key)
|
@@ -85,12 +115,14 @@ module ActionView
|
|
85
115
|
end
|
86
116
|
|
87
117
|
def tag_option(key, value, escape)
|
88
|
-
|
118
|
+
case value
|
119
|
+
when Array, Hash
|
120
|
+
value = TagHelper.build_tag_values(value) if key.to_s == "class"
|
89
121
|
value = escape ? safe_join(value, " ") : value.join(" ")
|
90
122
|
else
|
91
|
-
value = escape ? ERB::Util.unwrapped_html_escape(value)
|
123
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
92
124
|
end
|
93
|
-
value.gsub
|
125
|
+
value = value.gsub('"', """) if value.include?('"')
|
94
126
|
%(#{key}="#{value}")
|
95
127
|
end
|
96
128
|
|
@@ -152,8 +184,8 @@ module ActionView
|
|
152
184
|
# tag.input type: 'text', disabled: true
|
153
185
|
# # => <input type="text" disabled="disabled">
|
154
186
|
#
|
155
|
-
# HTML5 <tt>data-*</tt> attributes can be set with a
|
156
|
-
# pointing to a hash of sub-attributes.
|
187
|
+
# HTML5 <tt>data-*</tt> and <tt>aria-*</tt> attributes can be set with a
|
188
|
+
# single +data+ or +aria+ key pointing to a hash of sub-attributes.
|
157
189
|
#
|
158
190
|
# To play nicely with JavaScript conventions, sub-attributes are dasherized.
|
159
191
|
#
|
@@ -233,6 +265,9 @@ module ActionView
|
|
233
265
|
#
|
234
266
|
# tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
|
235
267
|
# # => <div data-name="Stephen" data-city-state="["Chicago","IL"]" />
|
268
|
+
#
|
269
|
+
# tag("div", class: { highlight: current_user.admin? })
|
270
|
+
# # => <div class="highlight" />
|
236
271
|
def tag(name = nil, options = nil, open = false, escape = true)
|
237
272
|
if name.nil?
|
238
273
|
tag_builder
|
@@ -260,6 +295,8 @@ module ActionView
|
|
260
295
|
# # => <div class="strong"><p>Hello world!</p></div>
|
261
296
|
# content_tag(:div, "Hello world!", class: ["strong", "highlight"])
|
262
297
|
# # => <div class="strong highlight">Hello world!</div>
|
298
|
+
# content_tag(:div, "Hello world!", class: ["strong", { highlight: current_user.admin? }])
|
299
|
+
# # => <div class="strong highlight">Hello world!</div>
|
263
300
|
# content_tag("select", options, multiple: true)
|
264
301
|
# # => <select multiple="multiple">...options...</select>
|
265
302
|
#
|
@@ -276,6 +313,24 @@ module ActionView
|
|
276
313
|
end
|
277
314
|
end
|
278
315
|
|
316
|
+
# Returns a string of tokens built from +args+.
|
317
|
+
#
|
318
|
+
# ==== Examples
|
319
|
+
# token_list("foo", "bar")
|
320
|
+
# # => "foo bar"
|
321
|
+
# token_list("foo", "foo bar")
|
322
|
+
# # => "foo bar"
|
323
|
+
# token_list({ foo: true, bar: false })
|
324
|
+
# # => "foo"
|
325
|
+
# token_list(nil, false, 123, "", "foo", { bar: true })
|
326
|
+
# # => "123 foo bar"
|
327
|
+
def token_list(*args)
|
328
|
+
tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
|
329
|
+
|
330
|
+
safe_join(tokens, " ")
|
331
|
+
end
|
332
|
+
alias_method :class_names, :token_list
|
333
|
+
|
279
334
|
# Returns a CDATA section with the given +content+. CDATA sections
|
280
335
|
# are used to escape blocks of text containing characters which would
|
281
336
|
# otherwise be recognized as markup. CDATA sections begin with the string
|
@@ -306,6 +361,26 @@ module ActionView
|
|
306
361
|
end
|
307
362
|
|
308
363
|
private
|
364
|
+
def build_tag_values(*args)
|
365
|
+
tag_values = []
|
366
|
+
|
367
|
+
args.each do |tag_value|
|
368
|
+
case tag_value
|
369
|
+
when Hash
|
370
|
+
tag_value.each do |key, val|
|
371
|
+
tag_values << key.to_s if val && key.present?
|
372
|
+
end
|
373
|
+
when Array
|
374
|
+
tag_values.concat build_tag_values(*tag_value)
|
375
|
+
else
|
376
|
+
tag_values << tag_value.to_s if tag_value.present?
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
tag_values
|
381
|
+
end
|
382
|
+
module_function :build_tag_values
|
383
|
+
|
309
384
|
def tag_builder
|
310
385
|
@tag_builder ||= TagBuilder.new(self)
|
311
386
|
end
|
@@ -105,7 +105,7 @@ module ActionView
|
|
105
105
|
end
|
106
106
|
|
107
107
|
def tag_name(multiple = false, index = nil)
|
108
|
-
# a little duplication to construct
|
108
|
+
# a little duplication to construct fewer strings
|
109
109
|
case
|
110
110
|
when @object_name.empty?
|
111
111
|
"#{sanitized_method_name}#{multiple ? "[]" : ""}"
|
@@ -117,7 +117,7 @@ module ActionView
|
|
117
117
|
end
|
118
118
|
|
119
119
|
def tag_id(index = nil)
|
120
|
-
# a little duplication to construct
|
120
|
+
# a little duplication to construct fewer strings
|
121
121
|
case
|
122
122
|
when @object_name.empty?
|
123
123
|
sanitized_method_name.dup
|
@@ -129,11 +129,11 @@ module ActionView
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def sanitized_object_name
|
132
|
-
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").
|
132
|
+
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").delete_suffix("_")
|
133
133
|
end
|
134
134
|
|
135
135
|
def sanitized_method_name
|
136
|
-
@sanitized_method_name ||= @method_name.
|
136
|
+
@sanitized_method_name ||= @method_name.delete_suffix("?")
|
137
137
|
end
|
138
138
|
|
139
139
|
def sanitized_value(value)
|
@@ -166,8 +166,11 @@ module ActionView
|
|
166
166
|
|
167
167
|
def add_options(option_tags, options, value = nil)
|
168
168
|
if options[:include_blank]
|
169
|
-
|
169
|
+
content = (options[:include_blank] if options[:include_blank].is_a?(String))
|
170
|
+
label = (" " unless content)
|
171
|
+
option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags
|
170
172
|
end
|
173
|
+
|
171
174
|
if value.blank? && options[:prompt]
|
172
175
|
tag_options = { value: "" }.tap do |prompt_opts|
|
173
176
|
prompt_opts[:disabled] = true if options[:disabled] == ""
|
@@ -175,6 +178,7 @@ module ActionView
|
|
175
178
|
end
|
176
179
|
option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
|
177
180
|
end
|
181
|
+
|
178
182
|
option_tags
|
179
183
|
end
|
180
184
|
|
@@ -13,7 +13,7 @@ module ActionView
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def render
|
16
|
-
error_wrapping(datetime_selector(@options, @html_options).
|
16
|
+
error_wrapping(datetime_selector(@options, @html_options).public_send("select_#{select_type}").html_safe)
|
17
17
|
end
|
18
18
|
|
19
19
|
class << self
|
@@ -58,7 +58,7 @@ module ActionView
|
|
58
58
|
time = Time.current
|
59
59
|
|
60
60
|
[:year, :month, :day, :hour, :min, :sec].each do |key|
|
61
|
-
default[key] ||= time.
|
61
|
+
default[key] ||= time.public_send(key)
|
62
62
|
end
|
63
63
|
|
64
64
|
Time.utc(
|