actionview 5.2.7.1 → 6.1.4.6
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 +250 -112
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -3
- data/lib/action_view/base.rb +81 -15
- data/lib/action_view/buffers.rb +15 -0
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +5 -9
- data/lib/action_view/dependency_tracker.rb +10 -4
- data/lib/action_view/digestor.rb +15 -22
- data/lib/action_view/flows.rb +0 -1
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +0 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +64 -47
- data/lib/action_view/helpers/asset_url_helper.rb +9 -6
- data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
- data/lib/action_view/helpers/cache_helper.rb +23 -22
- data/lib/action_view/helpers/capture_helper.rb +4 -0
- data/lib/action_view/helpers/csp_helper.rb +4 -2
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +73 -30
- data/lib/action_view/helpers/form_helper.rb +305 -37
- data/lib/action_view/helpers/form_options_helper.rb +23 -23
- data/lib/action_view/helpers/form_tag_helper.rb +19 -16
- data/lib/action_view/helpers/javascript_helper.rb +12 -11
- data/lib/action_view/helpers/number_helper.rb +14 -8
- data/lib/action_view/helpers/output_safety_helper.rb +1 -1
- data/lib/action_view/helpers/rendering_helper.rb +17 -7
- data/lib/action_view/helpers/sanitize_helper.rb +12 -18
- data/lib/action_view/helpers/tag_helper.rb +100 -55
- data/lib/action_view/helpers/tags/base.rb +18 -11
- data/lib/action_view/helpers/tags/check_box.rb +0 -1
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
- data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
- data/lib/action_view/helpers/tags/color_field.rb +1 -2
- data/lib/action_view/helpers/tags/date_field.rb +1 -2
- data/lib/action_view/helpers/tags/date_select.rb +2 -3
- data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
- data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
- data/lib/action_view/helpers/tags/label.rb +4 -1
- data/lib/action_view/helpers/tags/month_field.rb +1 -2
- data/lib/action_view/helpers/tags/radio_button.rb +0 -1
- data/lib/action_view/helpers/tags/select.rb +1 -2
- data/lib/action_view/helpers/tags/text_field.rb +0 -1
- data/lib/action_view/helpers/tags/time_field.rb +1 -2
- data/lib/action_view/helpers/tags/translator.rb +1 -6
- data/lib/action_view/helpers/tags/week_field.rb +1 -2
- data/lib/action_view/helpers/text_helper.rb +4 -5
- data/lib/action_view/helpers/translation_helper.rb +94 -54
- data/lib/action_view/helpers/url_helper.rb +136 -28
- data/lib/action_view/helpers.rb +0 -2
- data/lib/action_view/layouts.rb +8 -10
- data/lib/action_view/log_subscriber.rb +30 -15
- data/lib/action_view/lookup_context.rb +63 -35
- data/lib/action_view/path_set.rb +3 -12
- data/lib/action_view/railtie.rb +42 -26
- data/lib/action_view/record_identifier.rb +2 -3
- data/lib/action_view/renderer/abstract_renderer.rb +142 -11
- data/lib/action_view/renderer/collection_renderer.rb +196 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +61 -16
- data/lib/action_view/renderer/partial_renderer.rb +21 -273
- data/lib/action_view/renderer/renderer.rb +59 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +10 -8
- data/lib/action_view/renderer/template_renderer.rb +35 -27
- data/lib/action_view/rendering.rb +54 -33
- data/lib/action_view/routing_url_for.rb +13 -12
- data/lib/action_view/template/error.rb +30 -15
- data/lib/action_view/template/handlers/builder.rb +2 -2
- data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
- data/lib/action_view/template/handlers/erb.rb +16 -11
- data/lib/action_view/template/handlers/html.rb +1 -1
- data/lib/action_view/template/handlers/raw.rb +2 -2
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/html.rb +5 -6
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +191 -150
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +2 -3
- data/lib/action_view/template.rb +66 -75
- data/lib/action_view/test_case.rb +21 -29
- data/lib/action_view/testing/resolvers.rb +18 -27
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/view_paths.rb +59 -38
- data/lib/action_view.rb +7 -2
- data/lib/assets/compiled/rails-ujs.js +32 -6
- metadata +29 -18
- data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -34,7 +34,6 @@ module ActionView
|
|
34
34
|
end
|
35
35
|
|
36
36
|
private
|
37
|
-
|
38
37
|
def value
|
39
38
|
if @allow_method_names_outside_object
|
40
39
|
object.public_send @method_name if object && object.respond_to?(@method_name)
|
@@ -106,19 +105,19 @@ module ActionView
|
|
106
105
|
end
|
107
106
|
|
108
107
|
def tag_name(multiple = false, index = nil)
|
109
|
-
# a little duplication to construct
|
108
|
+
# a little duplication to construct fewer strings
|
110
109
|
case
|
111
110
|
when @object_name.empty?
|
112
|
-
"#{sanitized_method_name}#{"[]"
|
111
|
+
"#{sanitized_method_name}#{multiple ? "[]" : ""}"
|
113
112
|
when index
|
114
|
-
"#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]"
|
113
|
+
"#{@object_name}[#{index}][#{sanitized_method_name}]#{multiple ? "[]" : ""}"
|
115
114
|
else
|
116
|
-
"#{@object_name}[#{sanitized_method_name}]#{"[]"
|
115
|
+
"#{@object_name}[#{sanitized_method_name}]#{multiple ? "[]" : ""}"
|
117
116
|
end
|
118
117
|
end
|
119
118
|
|
120
119
|
def tag_id(index = nil)
|
121
|
-
# a little duplication to construct
|
120
|
+
# a little duplication to construct fewer strings
|
122
121
|
case
|
123
122
|
when @object_name.empty?
|
124
123
|
sanitized_method_name.dup
|
@@ -130,15 +129,15 @@ module ActionView
|
|
130
129
|
end
|
131
130
|
|
132
131
|
def sanitized_object_name
|
133
|
-
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").
|
132
|
+
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").delete_suffix("_")
|
134
133
|
end
|
135
134
|
|
136
135
|
def sanitized_method_name
|
137
|
-
@sanitized_method_name ||= @method_name.
|
136
|
+
@sanitized_method_name ||= @method_name.delete_suffix("?")
|
138
137
|
end
|
139
138
|
|
140
139
|
def sanitized_value(value)
|
141
|
-
value.to_s.gsub(
|
140
|
+
value.to_s.gsub(/[\s\.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase
|
142
141
|
end
|
143
142
|
|
144
143
|
def select_content_tag(option_tags, options, html_options)
|
@@ -167,11 +166,19 @@ module ActionView
|
|
167
166
|
|
168
167
|
def add_options(option_tags, options, value = nil)
|
169
168
|
if options[:include_blank]
|
170
|
-
|
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
|
171
172
|
end
|
173
|
+
|
172
174
|
if value.blank? && options[:prompt]
|
173
|
-
|
175
|
+
tag_options = { value: "" }.tap do |prompt_opts|
|
176
|
+
prompt_opts[:disabled] = true if options[:disabled] == ""
|
177
|
+
prompt_opts[:selected] = true if options[:selected] == ""
|
178
|
+
end
|
179
|
+
option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
|
174
180
|
end
|
181
|
+
|
175
182
|
option_tags
|
176
183
|
end
|
177
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
|
@@ -23,7 +23,6 @@ module ActionView
|
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
26
|
-
|
27
26
|
def select_type
|
28
27
|
self.class.select_type
|
29
28
|
end
|
@@ -59,7 +58,7 @@ module ActionView
|
|
59
58
|
time = Time.current
|
60
59
|
|
61
60
|
[:year, :month, :day, :hour, :min, :sec].each do |key|
|
62
|
-
default[key] ||= time.
|
61
|
+
default[key] ||= time.public_send(key)
|
63
62
|
end
|
64
63
|
|
65
64
|
Time.utc(
|
@@ -25,6 +25,10 @@ module ActionView
|
|
25
25
|
|
26
26
|
content
|
27
27
|
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
translation
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
34
|
def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
|
@@ -71,7 +75,6 @@ module ActionView
|
|
71
75
|
end
|
72
76
|
|
73
77
|
private
|
74
|
-
|
75
78
|
def render_component(builder)
|
76
79
|
builder.translation
|
77
80
|
end
|
@@ -15,7 +15,7 @@ module ActionView
|
|
15
15
|
|
16
16
|
def render
|
17
17
|
option_tags_options = {
|
18
|
-
selected: @options.fetch(:selected) { value },
|
18
|
+
selected: @options.fetch(:selected) { value.nil? ? "" : value },
|
19
19
|
disabled: @options[:disabled]
|
20
20
|
}
|
21
21
|
|
@@ -29,7 +29,6 @@ module ActionView
|
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
32
|
-
|
33
32
|
# Grouped choices look like this:
|
34
33
|
#
|
35
34
|
# [nil, []]
|
@@ -16,13 +16,8 @@ module ActionView
|
|
16
16
|
translated_attribute || human_attribute_name
|
17
17
|
end
|
18
18
|
|
19
|
-
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
20
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
21
|
-
protected
|
22
|
-
|
23
|
-
attr_reader :object_name, :method_and_value, :scope, :model
|
24
|
-
|
25
19
|
private
|
20
|
+
attr_reader :object_name, :method_and_value, :scope, :model
|
26
21
|
|
27
22
|
def i18n_default
|
28
23
|
if model
|
@@ -105,7 +105,7 @@ module ActionView
|
|
105
105
|
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
|
106
106
|
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
|
107
107
|
# as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
|
108
|
-
#
|
108
|
+
# <tt><mark>\1</mark></tt>) or passing a block that receives each matched term. By default +text+
|
109
109
|
# is sanitized to prevent possible XSS attacks. If the input is trustworthy, passing false
|
110
110
|
# for <tt>:sanitize</tt> will turn sanitizing off.
|
111
111
|
#
|
@@ -188,7 +188,7 @@ module ActionView
|
|
188
188
|
|
189
189
|
unless separator.empty?
|
190
190
|
text.split(separator).each do |value|
|
191
|
-
if value.match(regex)
|
191
|
+
if value.match?(regex)
|
192
192
|
phrase = value
|
193
193
|
break
|
194
194
|
end
|
@@ -228,7 +228,7 @@ module ActionView
|
|
228
228
|
# pluralize(2, 'Person', locale: :de)
|
229
229
|
# # => 2 Personen
|
230
230
|
def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
|
231
|
-
word = if
|
231
|
+
word = if count == 1 || count.to_s.match?(/^1(\.0+)?$/)
|
232
232
|
singular
|
233
233
|
else
|
234
234
|
plural || singular.pluralize(locale)
|
@@ -259,7 +259,7 @@ module ActionView
|
|
259
259
|
# # => Once\r\nupon\r\na\r\ntime
|
260
260
|
def word_wrap(text, line_width: 80, break_sequence: "\n")
|
261
261
|
text.split("\n").collect! do |line|
|
262
|
-
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").
|
262
|
+
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").rstrip : line
|
263
263
|
end * break_sequence
|
264
264
|
end
|
265
265
|
|
@@ -426,7 +426,6 @@ module ActionView
|
|
426
426
|
end
|
427
427
|
|
428
428
|
private
|
429
|
-
|
430
429
|
def next_index
|
431
430
|
step_index(1)
|
432
431
|
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "action_view/helpers/tag_helper"
|
4
|
-
require "active_support/core_ext/
|
5
|
-
require "i18n/exceptions"
|
4
|
+
require "active_support/core_ext/symbol/starts_ends_with"
|
6
5
|
|
7
6
|
module ActionView
|
8
7
|
# = Action View Translation Helpers
|
@@ -57,74 +56,67 @@ module ActionView
|
|
57
56
|
# that include HTML tags so that you know what kind of output to expect
|
58
57
|
# when you call translate in a template and translators know which keys
|
59
58
|
# they can provide HTML values for.
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
59
|
+
#
|
60
|
+
# To access the translated text along with the fully resolved
|
61
|
+
# translation key, <tt>translate</tt> accepts a block:
|
62
|
+
#
|
63
|
+
# <%= translate(".relative_key") do |translation, resolved_key| %>
|
64
|
+
# <span title="<%= resolved_key %>"><%= translation %></span>
|
65
|
+
# <% end %>
|
66
|
+
#
|
67
|
+
# This enables annotate translated text to be aware of the scope it was
|
68
|
+
# resolved against.
|
69
|
+
#
|
70
|
+
def translate(key, **options)
|
71
|
+
return key.map { |k| translate(k, **options) } if key.is_a?(Array)
|
72
|
+
key = key&.to_s unless key.is_a?(Symbol)
|
68
73
|
|
69
|
-
|
70
|
-
|
71
|
-
# Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
|
72
|
-
if options[:raise] == false
|
73
|
-
raise_error = false
|
74
|
-
i18n_raise = false
|
75
|
-
else
|
76
|
-
raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations
|
77
|
-
i18n_raise = true
|
74
|
+
alternatives = if options.key?(:default)
|
75
|
+
options[:default].is_a?(Array) ? options.delete(:default).compact : [options.delete(:default)]
|
78
76
|
end
|
79
77
|
|
80
|
-
if
|
81
|
-
|
78
|
+
options[:raise] = true if options[:raise].nil? && ActionView::Base.raise_on_missing_translations
|
79
|
+
default = MISSING_TRANSLATION
|
82
80
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
end
|
81
|
+
translation = while key || alternatives.present?
|
82
|
+
if alternatives.blank? && !options[:raise].nil?
|
83
|
+
default = NO_DEFAULT # let I18n handle missing translation
|
87
84
|
end
|
88
85
|
|
89
|
-
|
90
|
-
|
91
|
-
translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
|
86
|
+
key = scope_key_by_partial(key)
|
92
87
|
|
93
|
-
if
|
94
|
-
options
|
88
|
+
if html_safe_translation_key?(key)
|
89
|
+
html_safe_options ||= html_escape_translation_options(options)
|
90
|
+
translated = I18n.translate(key, **html_safe_options, default: default)
|
91
|
+
break html_safe_translation(translated) unless translated.equal?(MISSING_TRANSLATION)
|
95
92
|
else
|
96
|
-
|
93
|
+
translated = I18n.translate(key, **options, default: default)
|
94
|
+
break translated unless translated.equal?(MISSING_TRANSLATION)
|
97
95
|
end
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
rescue I18n::MissingTranslationData => e
|
102
|
-
if remaining_defaults.present?
|
103
|
-
translate remaining_defaults.shift, options.merge(default: remaining_defaults)
|
104
|
-
else
|
105
|
-
raise e if raise_error
|
106
|
-
|
107
|
-
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
|
108
|
-
title = "translation missing: #{keys.join('.')}".dup
|
109
|
-
|
110
|
-
interpolations = options.except(:default, :scope)
|
111
|
-
if interpolations.any?
|
112
|
-
title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(", ")
|
96
|
+
|
97
|
+
if alternatives.present? && !alternatives.first.is_a?(Symbol)
|
98
|
+
break alternatives.first && I18n.translate(**options, default: alternatives)
|
113
99
|
end
|
114
100
|
|
115
|
-
|
101
|
+
first_key ||= key
|
102
|
+
key = alternatives&.shift
|
103
|
+
end
|
116
104
|
|
117
|
-
|
105
|
+
if key.nil? && !first_key.nil?
|
106
|
+
translation = missing_translation(first_key, options)
|
107
|
+
key = first_key
|
118
108
|
end
|
109
|
+
|
110
|
+
block_given? ? yield(translation, key) : translation
|
119
111
|
end
|
120
112
|
alias :t :translate
|
121
113
|
|
122
114
|
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
|
123
115
|
#
|
124
|
-
# See
|
116
|
+
# See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
|
125
117
|
# for more information.
|
126
|
-
def localize(
|
127
|
-
I18n.localize(
|
118
|
+
def localize(object, **options)
|
119
|
+
I18n.localize(object, **options)
|
128
120
|
end
|
129
121
|
alias :l :localize
|
130
122
|
|
@@ -132,10 +124,19 @@ module ActionView
|
|
132
124
|
MISSING_TRANSLATION = Object.new
|
133
125
|
private_constant :MISSING_TRANSLATION
|
134
126
|
|
127
|
+
NO_DEFAULT = [].freeze
|
128
|
+
private_constant :NO_DEFAULT
|
129
|
+
|
130
|
+
def self.i18n_option?(name)
|
131
|
+
(@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name)
|
132
|
+
end
|
133
|
+
|
135
134
|
def scope_key_by_partial(key)
|
136
|
-
if key
|
135
|
+
if key&.start_with?(".")
|
137
136
|
if @virtual_path
|
138
|
-
@
|
137
|
+
@_scope_key_by_partial_cache ||= {}
|
138
|
+
@_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
|
139
|
+
"#{@_scope_key_by_partial_cache[@virtual_path]}#{key}"
|
139
140
|
else
|
140
141
|
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
|
141
142
|
end
|
@@ -144,8 +145,47 @@ module ActionView
|
|
144
145
|
end
|
145
146
|
end
|
146
147
|
|
148
|
+
def html_escape_translation_options(options)
|
149
|
+
return options if options.empty?
|
150
|
+
html_safe_options = options.dup
|
151
|
+
|
152
|
+
options.each do |name, value|
|
153
|
+
unless TranslationHelper.i18n_option?(name) || (name == :count && value.is_a?(Numeric))
|
154
|
+
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
html_safe_options
|
159
|
+
end
|
160
|
+
|
147
161
|
def html_safe_translation_key?(key)
|
148
|
-
/(
|
162
|
+
/(?:_|\b)html\z/.match?(key)
|
163
|
+
end
|
164
|
+
|
165
|
+
def html_safe_translation(translation)
|
166
|
+
if translation.respond_to?(:map)
|
167
|
+
translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
|
168
|
+
else
|
169
|
+
translation.respond_to?(:html_safe) ? translation.html_safe : translation
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def missing_translation(key, options)
|
174
|
+
keys = I18n.normalize_keys(options[:locale] || I18n.locale, key, options[:scope])
|
175
|
+
|
176
|
+
title = +"translation missing: #{keys.join(".")}"
|
177
|
+
|
178
|
+
options.each do |name, value|
|
179
|
+
unless name == :scope
|
180
|
+
title << ", " << name.to_s << ": " << ERB::Util.html_escape(value)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
if ActionView::Base.debug_missing_translation
|
185
|
+
content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
|
186
|
+
else
|
187
|
+
title
|
188
|
+
end
|
149
189
|
end
|
150
190
|
end
|
151
191
|
end
|