omg-actionview 8.0.0.alpha1

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.
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