actionview 5.2.3

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.

Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +142 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +38 -0
  5. data/lib/action_view.rb +97 -0
  6. data/lib/action_view/base.rb +215 -0
  7. data/lib/action_view/buffers.rb +52 -0
  8. data/lib/action_view/context.rb +36 -0
  9. data/lib/action_view/dependency_tracker.rb +175 -0
  10. data/lib/action_view/digestor.rb +134 -0
  11. data/lib/action_view/flows.rb +76 -0
  12. data/lib/action_view/gem_version.rb +17 -0
  13. data/lib/action_view/helpers.rb +68 -0
  14. data/lib/action_view/helpers/active_model_helper.rb +55 -0
  15. data/lib/action_view/helpers/asset_tag_helper.rb +511 -0
  16. data/lib/action_view/helpers/asset_url_helper.rb +469 -0
  17. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  18. data/lib/action_view/helpers/cache_helper.rb +263 -0
  19. data/lib/action_view/helpers/capture_helper.rb +212 -0
  20. data/lib/action_view/helpers/controller_helper.rb +36 -0
  21. data/lib/action_view/helpers/csp_helper.rb +24 -0
  22. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  23. data/lib/action_view/helpers/date_helper.rb +1156 -0
  24. data/lib/action_view/helpers/debug_helper.rb +36 -0
  25. data/lib/action_view/helpers/form_helper.rb +2337 -0
  26. data/lib/action_view/helpers/form_options_helper.rb +887 -0
  27. data/lib/action_view/helpers/form_tag_helper.rb +917 -0
  28. data/lib/action_view/helpers/javascript_helper.rb +94 -0
  29. data/lib/action_view/helpers/number_helper.rb +451 -0
  30. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  31. data/lib/action_view/helpers/record_tag_helper.rb +23 -0
  32. data/lib/action_view/helpers/rendering_helper.rb +99 -0
  33. data/lib/action_view/helpers/sanitize_helper.rb +177 -0
  34. data/lib/action_view/helpers/tag_helper.rb +313 -0
  35. data/lib/action_view/helpers/tags.rb +44 -0
  36. data/lib/action_view/helpers/tags/base.rb +192 -0
  37. data/lib/action_view/helpers/tags/check_box.rb +66 -0
  38. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  39. data/lib/action_view/helpers/tags/collection_check_boxes.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_helpers.rb +119 -0
  41. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  42. data/lib/action_view/helpers/tags/collection_select.rb +30 -0
  43. data/lib/action_view/helpers/tags/color_field.rb +27 -0
  44. data/lib/action_view/helpers/tags/date_field.rb +15 -0
  45. data/lib/action_view/helpers/tags/date_select.rb +74 -0
  46. data/lib/action_view/helpers/tags/datetime_field.rb +32 -0
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +21 -0
  48. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  49. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  50. data/lib/action_view/helpers/tags/file_field.rb +10 -0
  51. data/lib/action_view/helpers/tags/grouped_collection_select.rb +31 -0
  52. data/lib/action_view/helpers/tags/hidden_field.rb +10 -0
  53. data/lib/action_view/helpers/tags/label.rb +81 -0
  54. data/lib/action_view/helpers/tags/month_field.rb +15 -0
  55. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  56. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  58. data/lib/action_view/helpers/tags/radio_button.rb +33 -0
  59. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  60. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  61. data/lib/action_view/helpers/tags/select.rb +43 -0
  62. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  63. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  64. data/lib/action_view/helpers/tags/text_field.rb +34 -0
  65. data/lib/action_view/helpers/tags/time_field.rb +15 -0
  66. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  67. data/lib/action_view/helpers/tags/time_zone_select.rb +22 -0
  68. data/lib/action_view/helpers/tags/translator.rb +44 -0
  69. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  70. data/lib/action_view/helpers/tags/week_field.rb +15 -0
  71. data/lib/action_view/helpers/text_helper.rb +486 -0
  72. data/lib/action_view/helpers/translation_helper.rb +141 -0
  73. data/lib/action_view/helpers/url_helper.rb +676 -0
  74. data/lib/action_view/layouts.rb +433 -0
  75. data/lib/action_view/locale/en.yml +56 -0
  76. data/lib/action_view/log_subscriber.rb +96 -0
  77. data/lib/action_view/lookup_context.rb +274 -0
  78. data/lib/action_view/model_naming.rb +14 -0
  79. data/lib/action_view/path_set.rb +100 -0
  80. data/lib/action_view/railtie.rb +82 -0
  81. data/lib/action_view/record_identifier.rb +112 -0
  82. data/lib/action_view/renderer/abstract_renderer.rb +55 -0
  83. data/lib/action_view/renderer/partial_renderer.rb +552 -0
  84. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +57 -0
  85. data/lib/action_view/renderer/renderer.rb +56 -0
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
  87. data/lib/action_view/renderer/template_renderer.rb +102 -0
  88. data/lib/action_view/rendering.rb +151 -0
  89. data/lib/action_view/routing_url_for.rb +145 -0
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template.rb +361 -0
  92. data/lib/action_view/template/error.rb +141 -0
  93. data/lib/action_view/template/handlers.rb +66 -0
  94. data/lib/action_view/template/handlers/builder.rb +25 -0
  95. data/lib/action_view/template/handlers/erb.rb +74 -0
  96. data/lib/action_view/template/handlers/erb/erubi.rb +83 -0
  97. data/lib/action_view/template/handlers/html.rb +11 -0
  98. data/lib/action_view/template/handlers/raw.rb +11 -0
  99. data/lib/action_view/template/html.rb +34 -0
  100. data/lib/action_view/template/resolver.rb +391 -0
  101. data/lib/action_view/template/text.rb +33 -0
  102. data/lib/action_view/template/types.rb +57 -0
  103. data/lib/action_view/test_case.rb +300 -0
  104. data/lib/action_view/testing/resolvers.rb +54 -0
  105. data/lib/action_view/version.rb +10 -0
  106. data/lib/action_view/view_paths.rb +105 -0
  107. data/lib/assets/compiled/rails-ujs.js +720 -0
  108. metadata +255 -0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ # Returns the version of the currently loaded Action View as a <tt>Gem::Version</tt>
5
+ def self.gem_version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 5
11
+ MINOR = 2
12
+ TINY = 3
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
+ end
17
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/benchmarkable"
4
+
5
+ module ActionView #:nodoc:
6
+ module Helpers #:nodoc:
7
+ extend ActiveSupport::Autoload
8
+
9
+ autoload :ActiveModelHelper
10
+ autoload :AssetTagHelper
11
+ autoload :AssetUrlHelper
12
+ autoload :AtomFeedHelper
13
+ autoload :CacheHelper
14
+ autoload :CaptureHelper
15
+ autoload :ControllerHelper
16
+ autoload :CspHelper
17
+ autoload :CsrfHelper
18
+ autoload :DateHelper
19
+ autoload :DebugHelper
20
+ autoload :FormHelper
21
+ autoload :FormOptionsHelper
22
+ autoload :FormTagHelper
23
+ autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
24
+ autoload :NumberHelper
25
+ autoload :OutputSafetyHelper
26
+ autoload :RecordTagHelper
27
+ autoload :RenderingHelper
28
+ autoload :SanitizeHelper
29
+ autoload :TagHelper
30
+ autoload :TextHelper
31
+ autoload :TranslationHelper
32
+ autoload :UrlHelper
33
+ autoload :Tags
34
+
35
+ def self.eager_load!
36
+ super
37
+ Tags.eager_load!
38
+ end
39
+
40
+ extend ActiveSupport::Concern
41
+
42
+ include ActiveSupport::Benchmarkable
43
+ include ActiveModelHelper
44
+ include AssetTagHelper
45
+ include AssetUrlHelper
46
+ include AtomFeedHelper
47
+ include CacheHelper
48
+ include CaptureHelper
49
+ include ControllerHelper
50
+ include CspHelper
51
+ include CsrfHelper
52
+ include DateHelper
53
+ include DebugHelper
54
+ include FormHelper
55
+ include FormOptionsHelper
56
+ include FormTagHelper
57
+ include JavaScriptHelper
58
+ include NumberHelper
59
+ include OutputSafetyHelper
60
+ include RecordTagHelper
61
+ include RenderingHelper
62
+ include SanitizeHelper
63
+ include TagHelper
64
+ include TextHelper
65
+ include TranslationHelper
66
+ include UrlHelper
67
+ end
68
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/attribute_accessors"
4
+ require "active_support/core_ext/enumerable"
5
+
6
+ module ActionView
7
+ # = Active Model Helpers
8
+ module Helpers #:nodoc:
9
+ module ActiveModelHelper
10
+ end
11
+
12
+ module ActiveModelInstanceTag
13
+ def object
14
+ @active_model_object ||= begin
15
+ object = super
16
+ object.respond_to?(:to_model) ? object.to_model : object
17
+ end
18
+ end
19
+
20
+ def content_tag(type, options, *)
21
+ select_markup_helper?(type) ? super : error_wrapping(super)
22
+ end
23
+
24
+ def tag(type, options, *)
25
+ tag_generate_errors?(options) ? error_wrapping(super) : super
26
+ end
27
+
28
+ def error_wrapping(html_tag)
29
+ if object_has_errors?
30
+ Base.field_error_proc.call(html_tag, self)
31
+ else
32
+ html_tag
33
+ end
34
+ end
35
+
36
+ def error_message
37
+ object.errors[@method_name]
38
+ end
39
+
40
+ private
41
+
42
+ def object_has_errors?
43
+ object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
44
+ end
45
+
46
+ def select_markup_helper?(type)
47
+ ["optgroup", "option"].include?(type)
48
+ end
49
+
50
+ def tag_generate_errors?(options)
51
+ options["type"] != "hidden"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,511 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/extract_options"
4
+ require "active_support/core_ext/hash/keys"
5
+ require "active_support/core_ext/object/inclusion"
6
+ require "active_support/core_ext/object/try"
7
+ require "action_view/helpers/asset_url_helper"
8
+ require "action_view/helpers/tag_helper"
9
+
10
+ module ActionView
11
+ # = Action View Asset Tag Helpers
12
+ module Helpers #:nodoc:
13
+ # This module provides methods for generating HTML that links views to assets such
14
+ # as images, JavaScripts, stylesheets, and feeds. These methods do not verify
15
+ # the assets exist before linking to them:
16
+ #
17
+ # image_tag("rails.png")
18
+ # # => <img src="/assets/rails.png" />
19
+ # stylesheet_link_tag("application")
20
+ # # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
21
+ module AssetTagHelper
22
+ extend ActiveSupport::Concern
23
+
24
+ include AssetUrlHelper
25
+ include TagHelper
26
+
27
+ # Returns an HTML script tag for each of the +sources+ provided.
28
+ #
29
+ # Sources may be paths to JavaScript files. Relative paths are assumed to be relative
30
+ # to <tt>assets/javascripts</tt>, full paths are assumed to be relative to the document
31
+ # root. Relative paths are idiomatic, use absolute paths only when needed.
32
+ #
33
+ # When passing paths, the ".js" extension is optional. If you do not want ".js"
34
+ # appended to the path <tt>extname: false</tt> can be set on the options.
35
+ #
36
+ # You can modify the HTML attributes of the script tag by passing a hash as the
37
+ # last argument.
38
+ #
39
+ # When the Asset Pipeline is enabled, you can pass the name of your manifest as
40
+ # source, and include other JavaScript or CoffeeScript files inside the manifest.
41
+ #
42
+ # If the server supports Early Hints header links for these assets will be
43
+ # automatically pushed.
44
+ #
45
+ # ==== Options
46
+ #
47
+ # When the last parameter is a hash you can add HTML attributes using that
48
+ # parameter. The following options are supported:
49
+ #
50
+ # * <tt>:extname</tt> - Append an extension to the generated URL unless the extension
51
+ # already exists. This only applies for relative URLs.
52
+ # * <tt>:protocol</tt> - Sets the protocol of the generated URL. This option only
53
+ # applies when a relative URL and +host+ options are provided.
54
+ # * <tt>:host</tt> - When a relative URL is provided the host is added to the
55
+ # that path.
56
+ # * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
57
+ # when it is set to true.
58
+ # * <tt>:nonce<tt> - When set to true, adds an automatic nonce value if
59
+ # you have Content Security Policy enabled.
60
+ #
61
+ # ==== Examples
62
+ #
63
+ # javascript_include_tag "xmlhr"
64
+ # # => <script src="/assets/xmlhr.debug-1284139606.js"></script>
65
+ #
66
+ # javascript_include_tag "xmlhr", host: "localhost", protocol: "https"
67
+ # # => <script src="https://localhost/assets/xmlhr.debug-1284139606.js"></script>
68
+ #
69
+ # javascript_include_tag "template.jst", extname: false
70
+ # # => <script src="/assets/template.debug-1284139606.jst"></script>
71
+ #
72
+ # javascript_include_tag "xmlhr.js"
73
+ # # => <script src="/assets/xmlhr.debug-1284139606.js"></script>
74
+ #
75
+ # javascript_include_tag "common.javascript", "/elsewhere/cools"
76
+ # # => <script src="/assets/common.javascript.debug-1284139606.js"></script>
77
+ # # <script src="/elsewhere/cools.debug-1284139606.js"></script>
78
+ #
79
+ # javascript_include_tag "http://www.example.com/xmlhr"
80
+ # # => <script src="http://www.example.com/xmlhr"></script>
81
+ #
82
+ # javascript_include_tag "http://www.example.com/xmlhr.js"
83
+ # # => <script src="http://www.example.com/xmlhr.js"></script>
84
+ #
85
+ # javascript_include_tag "http://www.example.com/xmlhr.js", nonce: true
86
+ # # => <script src="http://www.example.com/xmlhr.js" nonce="..."></script>
87
+ def javascript_include_tag(*sources)
88
+ options = sources.extract_options!.stringify_keys
89
+ path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
90
+ early_hints_links = []
91
+
92
+ sources_tags = sources.uniq.map { |source|
93
+ href = path_to_javascript(source, path_options)
94
+ early_hints_links << "<#{href}>; rel=preload; as=script"
95
+ tag_options = {
96
+ "src" => href
97
+ }.merge!(options)
98
+ if tag_options["nonce"] == true
99
+ tag_options["nonce"] = content_security_policy_nonce
100
+ end
101
+ content_tag("script".freeze, "", tag_options)
102
+ }.join("\n").html_safe
103
+
104
+ request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) && request
105
+
106
+ sources_tags
107
+ end
108
+
109
+ # Returns a stylesheet link tag for the sources specified as arguments. If
110
+ # you don't specify an extension, <tt>.css</tt> will be appended automatically.
111
+ # You can modify the link attributes by passing a hash as the last argument.
112
+ # For historical reasons, the 'media' attribute will always be present and defaults
113
+ # to "screen", so you must explicitly set it to "all" for the stylesheet(s) to
114
+ # apply to all media types.
115
+ #
116
+ # If the server supports Early Hints header links for these assets will be
117
+ # automatically pushed.
118
+ #
119
+ # stylesheet_link_tag "style"
120
+ # # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
121
+ #
122
+ # stylesheet_link_tag "style.css"
123
+ # # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
124
+ #
125
+ # stylesheet_link_tag "http://www.example.com/style.css"
126
+ # # => <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
127
+ #
128
+ # stylesheet_link_tag "style", media: "all"
129
+ # # => <link href="/assets/style.css" media="all" rel="stylesheet" />
130
+ #
131
+ # stylesheet_link_tag "style", media: "print"
132
+ # # => <link href="/assets/style.css" media="print" rel="stylesheet" />
133
+ #
134
+ # stylesheet_link_tag "random.styles", "/css/stylish"
135
+ # # => <link href="/assets/random.styles" media="screen" rel="stylesheet" />
136
+ # # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
137
+ def stylesheet_link_tag(*sources)
138
+ options = sources.extract_options!.stringify_keys
139
+ path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
140
+ early_hints_links = []
141
+
142
+ sources_tags = sources.uniq.map { |source|
143
+ href = path_to_stylesheet(source, path_options)
144
+ early_hints_links << "<#{href}>; rel=preload; as=style"
145
+ tag_options = {
146
+ "rel" => "stylesheet",
147
+ "media" => "screen",
148
+ "href" => href
149
+ }.merge!(options)
150
+ tag(:link, tag_options)
151
+ }.join("\n").html_safe
152
+
153
+ request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) && request
154
+
155
+ sources_tags
156
+ end
157
+
158
+ # Returns a link tag that browsers and feed readers can use to auto-detect
159
+ # an RSS, Atom, or JSON feed. The +type+ can be <tt>:rss</tt> (default),
160
+ # <tt>:atom</tt>, or <tt>:json</tt>. Control the link options in url_for format
161
+ # using the +url_options+. You can modify the LINK tag itself in +tag_options+.
162
+ #
163
+ # ==== Options
164
+ #
165
+ # * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
166
+ # * <tt>:type</tt> - Override the auto-generated mime type
167
+ # * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
168
+ #
169
+ # ==== Examples
170
+ #
171
+ # auto_discovery_link_tag
172
+ # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
173
+ # auto_discovery_link_tag(:atom)
174
+ # # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
175
+ # auto_discovery_link_tag(:json)
176
+ # # => <link rel="alternate" type="application/json" title="JSON" href="http://www.currenthost.com/controller/action" />
177
+ # auto_discovery_link_tag(:rss, {action: "feed"})
178
+ # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
179
+ # auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"})
180
+ # # => <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
181
+ # auto_discovery_link_tag(:rss, {controller: "news", action: "feed"})
182
+ # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
183
+ # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"})
184
+ # # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed.rss" />
185
+ def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
186
+ if !(type == :rss || type == :atom || type == :json) && tag_options[:type].blank?
187
+ raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss, :atom, or :json.")
188
+ end
189
+
190
+ tag(
191
+ "link",
192
+ "rel" => tag_options[:rel] || "alternate",
193
+ "type" => tag_options[:type] || Template::Types[type].to_s,
194
+ "title" => tag_options[:title] || type.to_s.upcase,
195
+ "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(only_path: false)) : url_options
196
+ )
197
+ end
198
+
199
+ # Returns a link tag for a favicon managed by the asset pipeline.
200
+ #
201
+ # If a page has no link like the one generated by this helper, browsers
202
+ # ask for <tt>/favicon.ico</tt> automatically, and cache the file if the
203
+ # request succeeds. If the favicon changes it is hard to get it updated.
204
+ #
205
+ # To have better control applications may let the asset pipeline manage
206
+ # their favicon storing the file under <tt>app/assets/images</tt>, and
207
+ # using this helper to generate its corresponding link tag.
208
+ #
209
+ # The helper gets the name of the favicon file as first argument, which
210
+ # defaults to "favicon.ico", and also supports +:rel+ and +:type+ options
211
+ # to override their defaults, "shortcut icon" and "image/x-icon"
212
+ # respectively:
213
+ #
214
+ # favicon_link_tag
215
+ # # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon" />
216
+ #
217
+ # favicon_link_tag 'myicon.ico'
218
+ # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
219
+ #
220
+ # Mobile Safari looks for a different link tag, pointing to an image that
221
+ # will be used if you add the page to the home screen of an iOS device.
222
+ # The following call would generate such a tag:
223
+ #
224
+ # favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
225
+ # # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" />
226
+ def favicon_link_tag(source = "favicon.ico", options = {})
227
+ tag("link", {
228
+ rel: "shortcut icon",
229
+ type: "image/x-icon",
230
+ href: path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))
231
+ }.merge!(options.symbolize_keys))
232
+ end
233
+
234
+ # Returns a link tag that browsers can use to preload the +source+.
235
+ # The +source+ can be the path of a resource managed by asset pipeline,
236
+ # a full path, or an URI.
237
+ #
238
+ # ==== Options
239
+ #
240
+ # * <tt>:type</tt> - Override the auto-generated mime type, defaults to the mime type for +source+ extension.
241
+ # * <tt>:as</tt> - Override the auto-generated value for as attribute, calculated using +source+ extension and mime type.
242
+ # * <tt>:crossorigin</tt> - Specify the crossorigin attribute, required to load cross-origin resources.
243
+ # * <tt>:nopush</tt> - Specify if the use of server push is not desired for the resource. Defaults to +false+.
244
+ #
245
+ # ==== Examples
246
+ #
247
+ # preload_link_tag("custom_theme.css")
248
+ # # => <link rel="preload" href="/assets/custom_theme.css" as="style" type="text/css" />
249
+ #
250
+ # preload_link_tag("/videos/video.webm")
251
+ # # => <link rel="preload" href="/videos/video.mp4" as="video" type="video/webm" />
252
+ #
253
+ # preload_link_tag(post_path(format: :json), as: "fetch")
254
+ # # => <link rel="preload" href="/posts.json" as="fetch" type="application/json" />
255
+ #
256
+ # preload_link_tag("worker.js", as: "worker")
257
+ # # => <link rel="preload" href="/assets/worker.js" as="worker" type="text/javascript" />
258
+ #
259
+ # preload_link_tag("//example.com/font.woff2")
260
+ # # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="anonymous"/>
261
+ #
262
+ # preload_link_tag("//example.com/font.woff2", crossorigin: "use-credentials")
263
+ # # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="use-credentials" />
264
+ #
265
+ # preload_link_tag("/media/audio.ogg", nopush: true)
266
+ # # => <link rel="preload" href="/media/audio.ogg" as="audio" type="audio/ogg" />
267
+ #
268
+ def preload_link_tag(source, options = {})
269
+ href = asset_path(source, skip_pipeline: options.delete(:skip_pipeline))
270
+ extname = File.extname(source).downcase.delete(".")
271
+ mime_type = options.delete(:type) || Template::Types[extname].try(:to_s)
272
+ as_type = options.delete(:as) || resolve_link_as(extname, mime_type)
273
+ crossorigin = options.delete(:crossorigin)
274
+ crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
275
+ nopush = options.delete(:nopush) || false
276
+
277
+ link_tag = tag.link({
278
+ rel: "preload",
279
+ href: href,
280
+ as: as_type,
281
+ type: mime_type,
282
+ crossorigin: crossorigin
283
+ }.merge!(options.symbolize_keys))
284
+
285
+ early_hints_link = "<#{href}>; rel=preload; as=#{as_type}"
286
+ early_hints_link += "; type=#{mime_type}" if mime_type
287
+ early_hints_link += "; crossorigin=#{crossorigin}" if crossorigin
288
+ early_hints_link += "; nopush" if nopush
289
+
290
+ request.send_early_hints("Link" => early_hints_link) if respond_to?(:request) && request
291
+
292
+ link_tag
293
+ end
294
+
295
+ # Returns an HTML image tag for the +source+. The +source+ can be a full
296
+ # path, a file, or an Active Storage attachment.
297
+ #
298
+ # ==== Options
299
+ #
300
+ # You can add HTML attributes using the +options+. The +options+ supports
301
+ # additional keys for convenience and conformance:
302
+ #
303
+ # * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
304
+ # width="30" and height="45", and "50" becomes width="50" and height="50".
305
+ # <tt>:size</tt> will be ignored if the value is not in the correct format.
306
+ # * <tt>:srcset</tt> - If supplied as a hash or array of <tt>[source, descriptor]</tt>
307
+ # pairs, each image path will be expanded before the list is formatted as a string.
308
+ #
309
+ # ==== Examples
310
+ #
311
+ # Assets (images that are part of your app):
312
+ #
313
+ # image_tag("icon")
314
+ # # => <img src="/assets/icon" />
315
+ # image_tag("icon.png")
316
+ # # => <img src="/assets/icon.png" />
317
+ # image_tag("icon.png", size: "16x10", alt: "Edit Entry")
318
+ # # => <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" />
319
+ # image_tag("/icons/icon.gif", size: "16")
320
+ # # => <img src="/icons/icon.gif" width="16" height="16" />
321
+ # image_tag("/icons/icon.gif", height: '32', width: '32')
322
+ # # => <img height="32" src="/icons/icon.gif" width="32" />
323
+ # image_tag("/icons/icon.gif", class: "menu_icon")
324
+ # # => <img class="menu_icon" src="/icons/icon.gif" />
325
+ # image_tag("/icons/icon.gif", data: { title: 'Rails Application' })
326
+ # # => <img data-title="Rails Application" src="/icons/icon.gif" />
327
+ # image_tag("icon.png", srcset: { "icon_2x.png" => "2x", "icon_4x.png" => "4x" })
328
+ # # => <img src="/assets/icon.png" srcset="/assets/icon_2x.png 2x, /assets/icon_4x.png 4x">
329
+ # image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw")
330
+ # # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw">
331
+ #
332
+ # Active Storage (images that are uploaded by the users of your app):
333
+ #
334
+ # image_tag(user.avatar)
335
+ # # => <img src="/rails/active_storage/blobs/.../tiger.jpg" />
336
+ # image_tag(user.avatar.variant(resize: "100x100"))
337
+ # # => <img src="/rails/active_storage/variants/.../tiger.jpg" />
338
+ # image_tag(user.avatar.variant(resize: "100x100"), size: '100')
339
+ # # => <img width="100" height="100" src="/rails/active_storage/variants/.../tiger.jpg" />
340
+ def image_tag(source, options = {})
341
+ options = options.symbolize_keys
342
+ check_for_image_tag_errors(options)
343
+ skip_pipeline = options.delete(:skip_pipeline)
344
+
345
+ options[:src] = resolve_image_source(source, skip_pipeline)
346
+
347
+ if options[:srcset] && !options[:srcset].is_a?(String)
348
+ options[:srcset] = options[:srcset].map do |src_path, size|
349
+ src_path = path_to_image(src_path, skip_pipeline: skip_pipeline)
350
+ "#{src_path} #{size}"
351
+ end.join(", ")
352
+ end
353
+
354
+ options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
355
+ tag("img", options)
356
+ end
357
+
358
+ # Returns a string suitable for an HTML image tag alt attribute.
359
+ # The +src+ argument is meant to be an image file path.
360
+ # The method removes the basename of the file path and the digest,
361
+ # if any. It also removes hyphens and underscores from file names and
362
+ # replaces them with spaces, returning a space-separated, titleized
363
+ # string.
364
+ #
365
+ # ==== Examples
366
+ #
367
+ # image_alt('rails.png')
368
+ # # => Rails
369
+ #
370
+ # image_alt('hyphenated-file-name.png')
371
+ # # => Hyphenated file name
372
+ #
373
+ # image_alt('underscored_file_name.png')
374
+ # # => Underscored file name
375
+ def image_alt(src)
376
+ ActiveSupport::Deprecation.warn("image_alt is deprecated and will be removed from Rails 6.0. You must explicitly set alt text on images.")
377
+
378
+ File.basename(src, ".*".freeze).sub(/-[[:xdigit:]]{32,64}\z/, "".freeze).tr("-_".freeze, " ".freeze).capitalize
379
+ end
380
+
381
+ # Returns an HTML video tag for the +sources+. If +sources+ is a string,
382
+ # a single video tag will be returned. If +sources+ is an array, a video
383
+ # tag with nested source tags for each source will be returned. The
384
+ # +sources+ can be full paths or files that exist in your public videos
385
+ # directory.
386
+ #
387
+ # ==== Options
388
+ #
389
+ # When the last parameter is a hash you can add HTML attributes using that
390
+ # parameter. The following options are supported:
391
+ #
392
+ # * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
393
+ # before the video loads. The path is calculated like the +src+ of +image_tag+.
394
+ # * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
395
+ # width="30" and height="45", and "50" becomes width="50" and height="50".
396
+ # <tt>:size</tt> will be ignored if the value is not in the correct format.
397
+ # * <tt>:poster_skip_pipeline</tt> will bypass the asset pipeline when using
398
+ # the <tt>:poster</tt> option instead using an asset in the public folder.
399
+ #
400
+ # ==== Examples
401
+ #
402
+ # video_tag("trailer")
403
+ # # => <video src="/videos/trailer"></video>
404
+ # video_tag("trailer.ogg")
405
+ # # => <video src="/videos/trailer.ogg"></video>
406
+ # video_tag("trailer.ogg", controls: true, preload: 'none')
407
+ # # => <video preload="none" controls="controls" src="/videos/trailer.ogg"></video>
408
+ # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
409
+ # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video>
410
+ # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true)
411
+ # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="screenshot.png"></video>
412
+ # video_tag("/trailers/hd.avi", size: "16x16")
413
+ # # => <video src="/trailers/hd.avi" width="16" height="16"></video>
414
+ # video_tag("/trailers/hd.avi", size: "16")
415
+ # # => <video height="16" src="/trailers/hd.avi" width="16"></video>
416
+ # video_tag("/trailers/hd.avi", height: '32', width: '32')
417
+ # # => <video height="32" src="/trailers/hd.avi" width="32"></video>
418
+ # video_tag("trailer.ogg", "trailer.flv")
419
+ # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
420
+ # video_tag(["trailer.ogg", "trailer.flv"])
421
+ # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
422
+ # video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
423
+ # # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
424
+ def video_tag(*sources)
425
+ options = sources.extract_options!.symbolize_keys
426
+ public_poster_folder = options.delete(:poster_skip_pipeline)
427
+ sources << options
428
+ multiple_sources_tag_builder("video", sources) do |tag_options|
429
+ tag_options[:poster] = path_to_image(tag_options[:poster], skip_pipeline: public_poster_folder) if tag_options[:poster]
430
+ tag_options[:width], tag_options[:height] = extract_dimensions(tag_options.delete(:size)) if tag_options[:size]
431
+ end
432
+ end
433
+
434
+ # Returns an HTML audio tag for the +sources+. If +sources+ is a string,
435
+ # a single audio tag will be returned. If +sources+ is an array, an audio
436
+ # tag with nested source tags for each source will be returned. The
437
+ # +sources+ can be full paths or files that exist in your public audios
438
+ # directory.
439
+ #
440
+ # When the last parameter is a hash you can add HTML attributes using that
441
+ # parameter.
442
+ #
443
+ # audio_tag("sound")
444
+ # # => <audio src="/audios/sound"></audio>
445
+ # audio_tag("sound.wav")
446
+ # # => <audio src="/audios/sound.wav"></audio>
447
+ # audio_tag("sound.wav", autoplay: true, controls: true)
448
+ # # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio>
449
+ # audio_tag("sound.wav", "sound.mid")
450
+ # # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
451
+ def audio_tag(*sources)
452
+ multiple_sources_tag_builder("audio", sources)
453
+ end
454
+
455
+ private
456
+ def multiple_sources_tag_builder(type, sources)
457
+ options = sources.extract_options!.symbolize_keys
458
+ skip_pipeline = options.delete(:skip_pipeline)
459
+ sources.flatten!
460
+
461
+ yield options if block_given?
462
+
463
+ if sources.size > 1
464
+ content_tag(type, options) do
465
+ safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) }
466
+ end
467
+ else
468
+ options[:src] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline)
469
+ content_tag(type, nil, options)
470
+ end
471
+ end
472
+
473
+ def resolve_image_source(source, skip_pipeline)
474
+ if source.is_a?(Symbol) || source.is_a?(String)
475
+ path_to_image(source, skip_pipeline: skip_pipeline)
476
+ else
477
+ polymorphic_url(source)
478
+ end
479
+ rescue NoMethodError => e
480
+ raise ArgumentError, "Can't resolve image into URL: #{e}"
481
+ end
482
+
483
+ def extract_dimensions(size)
484
+ size = size.to_s
485
+ if /\A\d+x\d+\z/.match?(size)
486
+ size.split("x")
487
+ elsif /\A\d+\z/.match?(size)
488
+ [size, size]
489
+ end
490
+ end
491
+
492
+ def check_for_image_tag_errors(options)
493
+ if options[:size] && (options[:height] || options[:width])
494
+ raise ArgumentError, "Cannot pass a :size option with a :height or :width option"
495
+ end
496
+ end
497
+
498
+ def resolve_link_as(extname, mime_type)
499
+ if extname == "js"
500
+ "script"
501
+ elsif extname == "css"
502
+ "style"
503
+ elsif extname == "vtt"
504
+ "track"
505
+ elsif (type = mime_type.to_s.split("/")[0]) && type.in?(%w(audio video font))
506
+ type
507
+ end
508
+ end
509
+ end
510
+ end
511
+ end