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,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ class Base # :nodoc:
7
+ include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
8
+
9
+ attr_reader :object
10
+
11
+ def initialize(object_name, method_name, template_object, options = {})
12
+ @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
13
+ @template_object = template_object
14
+
15
+ @object_name.sub!(/\[\]$/, "") || @object_name.sub!(/\[\]\]$/, "]")
16
+ @object = retrieve_object(options.delete(:object))
17
+ @skip_default_ids = options.delete(:skip_default_ids)
18
+ @allow_method_names_outside_object = options.delete(:allow_method_names_outside_object)
19
+ @options = options
20
+
21
+ if Regexp.last_match
22
+ @generate_indexed_names = true
23
+ @auto_index = retrieve_autoindex(Regexp.last_match.pre_match)
24
+ else
25
+ @generate_indexed_names = false
26
+ @auto_index = nil
27
+ end
28
+ end
29
+
30
+ # This is what child classes implement.
31
+ def render
32
+ raise NotImplementedError, "Subclasses must implement a render method"
33
+ end
34
+
35
+ private
36
+ def value
37
+ return unless object
38
+
39
+ if @allow_method_names_outside_object
40
+ object.public_send @method_name if object.respond_to?(@method_name)
41
+ else
42
+ object.public_send @method_name
43
+ end
44
+ end
45
+
46
+ def value_before_type_cast
47
+ return unless object
48
+
49
+ method_before_type_cast = @method_name + "_before_type_cast"
50
+
51
+ if value_came_from_user? && object.respond_to?(method_before_type_cast)
52
+ object.public_send(method_before_type_cast)
53
+ else
54
+ value
55
+ end
56
+ end
57
+
58
+ def value_came_from_user?
59
+ method_name = "#{@method_name}_came_from_user?"
60
+ !object.respond_to?(method_name) || object.public_send(method_name)
61
+ end
62
+
63
+ def retrieve_object(object)
64
+ if object
65
+ object
66
+ elsif @template_object.instance_variable_defined?("@#{@object_name}")
67
+ @template_object.instance_variable_get("@#{@object_name}")
68
+ end
69
+ rescue NameError
70
+ # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
71
+ nil
72
+ end
73
+
74
+ def retrieve_autoindex(pre_match)
75
+ object = self.object || @template_object.instance_variable_get("@#{pre_match}")
76
+ if object && object.respond_to?(:to_param)
77
+ object.to_param
78
+ else
79
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
80
+ end
81
+ end
82
+
83
+ def add_default_name_and_id_for_value(tag_value, options)
84
+ if tag_value.nil?
85
+ add_default_name_and_id(options)
86
+ else
87
+ specified_id = options["id"]
88
+ add_default_name_and_id(options)
89
+
90
+ if specified_id.blank? && options["id"].present?
91
+ options["id"] += "_#{sanitized_value(tag_value)}"
92
+ end
93
+ end
94
+ end
95
+
96
+ def add_default_name_and_id(options)
97
+ index = name_and_id_index(options)
98
+ options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
99
+
100
+ if generate_ids?
101
+ options["id"] = options.fetch("id") { tag_id(index, options.delete("namespace")) }
102
+ if namespace = options.delete("namespace")
103
+ options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
104
+ end
105
+ end
106
+ end
107
+
108
+ def tag_name(multiple = false, index = nil)
109
+ @template_object.field_name(@object_name, sanitized_method_name, multiple: multiple, index: index)
110
+ end
111
+
112
+ def tag_id(index = nil, namespace = nil)
113
+ @template_object.field_id(@object_name, @method_name, index: index, namespace: namespace)
114
+ end
115
+
116
+ def sanitized_method_name
117
+ @sanitized_method_name ||= @method_name.delete_suffix("?")
118
+ end
119
+
120
+ def sanitized_value(value)
121
+ value.to_s.gsub(/[\s.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase
122
+ end
123
+
124
+ def name_and_id_index(options)
125
+ if options.key?("index")
126
+ options.delete("index") || ""
127
+ elsif @generate_indexed_names
128
+ @auto_index || ""
129
+ end
130
+ end
131
+
132
+ def generate_ids?
133
+ !@skip_default_ids
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tags/checkable"
4
+
5
+ module ActionView
6
+ module Helpers
7
+ module Tags # :nodoc:
8
+ class CheckBox < Base # :nodoc:
9
+ include Checkable
10
+
11
+ def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options)
12
+ @checked_value = checked_value
13
+ @unchecked_value = unchecked_value
14
+ super(object_name, method_name, template_object, options)
15
+ end
16
+
17
+ def render
18
+ options = @options.stringify_keys
19
+ options["type"] = "checkbox"
20
+ options["value"] = @checked_value
21
+ options["checked"] = "checked" if input_checked?(options)
22
+
23
+ if options["multiple"]
24
+ add_default_name_and_id_for_value(@checked_value, options)
25
+ options.delete("multiple")
26
+ else
27
+ add_default_name_and_id(options)
28
+ end
29
+
30
+ include_hidden = options.delete("include_hidden") { true }
31
+ checkbox = tag("input", options)
32
+
33
+ if include_hidden
34
+ hidden = hidden_field_for_checkbox(options)
35
+ hidden + checkbox
36
+ else
37
+ checkbox
38
+ end
39
+ end
40
+
41
+ private
42
+ def checked?(value)
43
+ case value
44
+ when TrueClass, FalseClass
45
+ value == !!@checked_value
46
+ when NilClass
47
+ false
48
+ when String
49
+ value == @checked_value
50
+ else
51
+ if value.respond_to?(:include?)
52
+ value.include?(@checked_value)
53
+ else
54
+ value.to_i == @checked_value.to_i
55
+ end
56
+ end
57
+ end
58
+
59
+ def hidden_field_for_checkbox(options)
60
+ @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value, "autocomplete" => "off")) : "".html_safe
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ module Checkable # :nodoc:
7
+ def input_checked?(options)
8
+ if options.has_key?("checked")
9
+ checked = options.delete "checked"
10
+ checked == true || checked == "checked"
11
+ else
12
+ checked?(value)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tags/collection_helpers"
4
+
5
+ module ActionView
6
+ module Helpers
7
+ module Tags # :nodoc:
8
+ class CollectionCheckBoxes < Base # :nodoc:
9
+ include CollectionHelpers
10
+ include FormOptionsHelper
11
+
12
+ class CheckBoxBuilder < Builder # :nodoc:
13
+ def checkbox(extra_html_options = {})
14
+ html_options = extra_html_options.merge(@input_html_options)
15
+ html_options[:multiple] = true
16
+ html_options[:skip_default_ids] = false
17
+ @template_object.checkbox(@object_name, @method_name, html_options, @value, nil)
18
+ end
19
+ alias_method :check_box, :checkbox
20
+ end
21
+
22
+ def render(&block)
23
+ render_collection_for(CheckBoxBuilder, &block)
24
+ end
25
+
26
+ private
27
+ def render_component(builder)
28
+ builder.checkbox + builder.label
29
+ end
30
+
31
+ def hidden_field_name
32
+ "#{super}[]"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ module CollectionHelpers # :nodoc:
7
+ class Builder # :nodoc:
8
+ attr_reader :object, :text, :value
9
+
10
+ def initialize(template_object, object_name, method_name, object,
11
+ sanitized_attribute_name, text, value, input_html_options)
12
+ @template_object = template_object
13
+ @object_name = object_name
14
+ @method_name = method_name
15
+ @object = object
16
+ @sanitized_attribute_name = sanitized_attribute_name
17
+ @text = text
18
+ @value = value
19
+ @input_html_options = input_html_options
20
+ end
21
+
22
+ def label(label_html_options = {}, &block)
23
+ html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options)
24
+ html_options[:for] ||= @input_html_options[:id] if @input_html_options[:id]
25
+
26
+ @template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block)
27
+ end
28
+ end
29
+
30
+ def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
31
+ @collection = collection
32
+ @value_method = value_method
33
+ @text_method = text_method
34
+ @html_options = html_options
35
+
36
+ super(object_name, method_name, template_object, options)
37
+ end
38
+
39
+ private
40
+ def instantiate_builder(builder_class, item, value, text, html_options)
41
+ builder_class.new(@template_object, @object_name, @method_name, item,
42
+ sanitize_attribute_name(value), text, value, html_options)
43
+ end
44
+
45
+ # Generate default options for collection helpers, such as :checked and
46
+ # :disabled.
47
+ def default_html_options_for_collection(item, value)
48
+ html_options = @html_options.dup
49
+
50
+ [:checked, :selected, :disabled, :readonly].each do |option|
51
+ current_value = @options[option]
52
+ next if current_value.nil?
53
+
54
+ accept = if current_value.respond_to?(:call)
55
+ current_value.call(item)
56
+ else
57
+ Array(current_value).map(&:to_s).include?(value.to_s)
58
+ end
59
+
60
+ if accept
61
+ html_options[option] = true
62
+ elsif option == :checked
63
+ html_options[option] = false
64
+ end
65
+ end
66
+
67
+ html_options[:object] = @object
68
+ html_options
69
+ end
70
+
71
+ def sanitize_attribute_name(value)
72
+ "#{sanitized_method_name}_#{sanitized_value(value)}"
73
+ end
74
+
75
+ def render_collection
76
+ @collection.map do |item|
77
+ value = value_for_collection(item, @value_method)
78
+ text = value_for_collection(item, @text_method)
79
+ default_html_options = default_html_options_for_collection(item, value)
80
+ additional_html_options = option_html_attributes(item)
81
+
82
+ yield item, value, text, default_html_options.merge(additional_html_options)
83
+ end.join.html_safe
84
+ end
85
+
86
+ def render_collection_for(builder_class, &block)
87
+ options = @options.stringify_keys
88
+ rendered_collection = render_collection do |item, value, text, default_html_options|
89
+ builder = instantiate_builder(builder_class, item, value, text, default_html_options)
90
+
91
+ if block_given?
92
+ @template_object.capture(builder, &block)
93
+ else
94
+ render_component(builder)
95
+ end
96
+ end
97
+
98
+ # Prepend a hidden field to make sure something will be sent back to the
99
+ # server if all radio buttons are unchecked.
100
+ if options.fetch("include_hidden", true)
101
+ hidden_field + rendered_collection
102
+ else
103
+ rendered_collection
104
+ end
105
+ end
106
+
107
+ def hidden_field
108
+ hidden_name = @html_options[:name] || hidden_field_name
109
+ @template_object.hidden_field_tag(hidden_name, "", id: nil)
110
+ end
111
+
112
+ def hidden_field_name
113
+ "#{tag_name(false, @options[:index])}"
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tags/collection_helpers"
4
+
5
+ module ActionView
6
+ module Helpers
7
+ module Tags # :nodoc:
8
+ class CollectionRadioButtons < Base # :nodoc:
9
+ include CollectionHelpers
10
+ include FormOptionsHelper
11
+
12
+ class RadioButtonBuilder < Builder # :nodoc:
13
+ def radio_button(extra_html_options = {})
14
+ html_options = extra_html_options.merge(@input_html_options)
15
+ html_options[:skip_default_ids] = false
16
+ @template_object.radio_button(@object_name, @method_name, @value, html_options)
17
+ end
18
+ end
19
+
20
+ def render(&block)
21
+ render_collection_for(RadioButtonBuilder, &block)
22
+ end
23
+
24
+ private
25
+ def render_component(builder)
26
+ builder.radio_button + builder.label
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ class CollectionSelect < Base # :nodoc:
7
+ include SelectRenderer
8
+ include FormOptionsHelper
9
+
10
+ def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
11
+ @collection = collection
12
+ @value_method = value_method
13
+ @text_method = text_method
14
+ @html_options = html_options
15
+
16
+ super(object_name, method_name, template_object, options)
17
+ end
18
+
19
+ def render
20
+ option_tags_options = {
21
+ selected: @options.fetch(:selected) { value },
22
+ disabled: @options[:disabled]
23
+ }
24
+
25
+ select_content_tag(
26
+ options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options),
27
+ @options, @html_options
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ class ColorField < TextField # :nodoc:
7
+ def render
8
+ options = @options.stringify_keys
9
+ options["value"] ||= validate_color_string(value)
10
+ @options = options
11
+ super
12
+ end
13
+
14
+ private
15
+ def validate_color_string(string)
16
+ regex = /#[0-9a-fA-F]{6}/
17
+ if regex.match?(string)
18
+ string.downcase
19
+ else
20
+ "#000000"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ class DateField < DatetimeField # :nodoc:
7
+ private
8
+ def format_datetime(value)
9
+ value&.strftime("%Y-%m-%d")
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/time/calculations"
4
+
5
+ module ActionView
6
+ module Helpers
7
+ module Tags # :nodoc:
8
+ class DateSelect < Base # :nodoc:
9
+ include SelectRenderer
10
+
11
+ def initialize(object_name, method_name, template_object, options, html_options)
12
+ @html_options = html_options
13
+
14
+ super(object_name, method_name, template_object, options)
15
+ end
16
+
17
+ def render
18
+ error_wrapping(datetime_selector(@options, @html_options).public_send("select_#{select_type}").html_safe)
19
+ end
20
+
21
+ class << self
22
+ def select_type
23
+ @select_type ||= name.split("::").last.sub("Select", "").downcase
24
+ end
25
+ end
26
+
27
+ private
28
+ def select_type
29
+ self.class.select_type
30
+ end
31
+
32
+ def datetime_selector(options, html_options)
33
+ datetime = options.fetch(:selected) { value || default_datetime(options) }
34
+ @auto_index ||= nil
35
+
36
+ options = options.dup
37
+ options[:field_name] = @method_name
38
+ options[:include_position] = true
39
+ options[:prefix] ||= @object_name
40
+ options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
41
+
42
+ DateTimeSelector.new(datetime, options, html_options)
43
+ end
44
+
45
+ def default_datetime(options)
46
+ return if options[:include_blank] || options[:prompt]
47
+
48
+ case options[:default]
49
+ when nil
50
+ Time.current
51
+ when Date, Time
52
+ options[:default]
53
+ else
54
+ default = options[:default].dup
55
+
56
+ # Rename :minute and :second to :min and :sec
57
+ default[:min] ||= default[:minute]
58
+ default[:sec] ||= default[:second]
59
+
60
+ time = Time.current
61
+
62
+ [:year, :month, :day, :hour, :min, :sec].each do |key|
63
+ default[key] ||= time.public_send(key)
64
+ end
65
+
66
+ Time.utc(
67
+ default[:year], default[:month], default[:day],
68
+ default[:hour], default[:min], default[:sec]
69
+ )
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ class DatetimeField < TextField # :nodoc:
7
+ def render
8
+ options = @options.stringify_keys
9
+ options["value"] = datetime_value(options["value"] || value)
10
+ options["min"] = format_datetime(parse_datetime(options["min"]))
11
+ options["max"] = format_datetime(parse_datetime(options["max"]))
12
+ @options = options
13
+ super
14
+ end
15
+
16
+ private
17
+ def datetime_value(value)
18
+ if value.is_a?(String)
19
+ value
20
+ else
21
+ format_datetime(value)
22
+ end
23
+ end
24
+
25
+ def format_datetime(value)
26
+ raise NotImplementedError
27
+ end
28
+
29
+ def parse_datetime(value)
30
+ if value.is_a?(String)
31
+ DateTime.parse(value) rescue nil
32
+ else
33
+ value
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ class DatetimeLocalField < 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
+
12
+ class << self
13
+ def field_type
14
+ @field_type ||= "datetime-local"
15
+ end
16
+ end
17
+
18
+ private
19
+ def format_datetime(value)
20
+ if @include_seconds
21
+ value&.strftime("%Y-%m-%dT%T")
22
+ else
23
+ value&.strftime("%Y-%m-%dT%H:%M")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ class DatetimeSelect < DateSelect # :nodoc:
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ class EmailField < TextField # :nodoc:
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ class FileField < TextField # :nodoc:
7
+ def render
8
+ include_hidden = @options.delete(:include_hidden)
9
+ options = @options.stringify_keys
10
+ add_default_name_and_id(options)
11
+
12
+ if options["multiple"] && include_hidden
13
+ hidden_field_for_multiple_file(options) + super
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ private
20
+ def hidden_field_for_multiple_file(options)
21
+ tag("input", "name" => options["name"], "type" => "hidden", "value" => "", "autocomplete" => "off")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end