actionview 6.0.0

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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +271 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/lib/action_view.rb +98 -0
  6. data/lib/action_view/base.rb +312 -0
  7. data/lib/action_view/buffers.rb +67 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +32 -0
  10. data/lib/action_view/dependency_tracker.rb +175 -0
  11. data/lib/action_view/digestor.rb +126 -0
  12. data/lib/action_view/flows.rb +76 -0
  13. data/lib/action_view/gem_version.rb +17 -0
  14. data/lib/action_view/helpers.rb +66 -0
  15. data/lib/action_view/helpers/active_model_helper.rb +55 -0
  16. data/lib/action_view/helpers/asset_tag_helper.rb +488 -0
  17. data/lib/action_view/helpers/asset_url_helper.rb +470 -0
  18. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  19. data/lib/action_view/helpers/cache_helper.rb +271 -0
  20. data/lib/action_view/helpers/capture_helper.rb +216 -0
  21. data/lib/action_view/helpers/controller_helper.rb +36 -0
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  24. data/lib/action_view/helpers/date_helper.rb +1200 -0
  25. data/lib/action_view/helpers/debug_helper.rb +36 -0
  26. data/lib/action_view/helpers/form_helper.rb +2569 -0
  27. data/lib/action_view/helpers/form_options_helper.rb +896 -0
  28. data/lib/action_view/helpers/form_tag_helper.rb +920 -0
  29. data/lib/action_view/helpers/javascript_helper.rb +95 -0
  30. data/lib/action_view/helpers/number_helper.rb +456 -0
  31. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  32. data/lib/action_view/helpers/rendering_helper.rb +101 -0
  33. data/lib/action_view/helpers/sanitize_helper.rb +171 -0
  34. data/lib/action_view/helpers/tag_helper.rb +314 -0
  35. data/lib/action_view/helpers/tags.rb +44 -0
  36. data/lib/action_view/helpers/tags/base.rb +196 -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 +39 -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 +145 -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 +316 -0
  78. data/lib/action_view/model_naming.rb +14 -0
  79. data/lib/action_view/path_set.rb +95 -0
  80. data/lib/action_view/railtie.rb +105 -0
  81. data/lib/action_view/record_identifier.rb +112 -0
  82. data/lib/action_view/renderer/abstract_renderer.rb +108 -0
  83. data/lib/action_view/renderer/partial_renderer.rb +563 -0
  84. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  85. data/lib/action_view/renderer/renderer.rb +68 -0
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
  87. data/lib/action_view/renderer/template_renderer.rb +108 -0
  88. data/lib/action_view/rendering.rb +171 -0
  89. data/lib/action_view/routing_url_for.rb +146 -0
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template.rb +393 -0
  92. data/lib/action_view/template/error.rb +161 -0
  93. data/lib/action_view/template/handlers.rb +92 -0
  94. data/lib/action_view/template/handlers/builder.rb +25 -0
  95. data/lib/action_view/template/handlers/erb.rb +84 -0
  96. data/lib/action_view/template/handlers/erb/erubi.rb +87 -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 +43 -0
  100. data/lib/action_view/template/inline.rb +22 -0
  101. data/lib/action_view/template/raw_file.rb +28 -0
  102. data/lib/action_view/template/resolver.rb +394 -0
  103. data/lib/action_view/template/sources.rb +13 -0
  104. data/lib/action_view/template/sources/file.rb +17 -0
  105. data/lib/action_view/template/text.rb +35 -0
  106. data/lib/action_view/template/types.rb +57 -0
  107. data/lib/action_view/test_case.rb +300 -0
  108. data/lib/action_view/testing/resolvers.rb +67 -0
  109. data/lib/action_view/unbound_template.rb +32 -0
  110. data/lib/action_view/version.rb +10 -0
  111. data/lib/action_view/view_paths.rb +129 -0
  112. data/lib/assets/compiled/rails-ujs.js +746 -0
  113. metadata +260 -0
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/output_safety"
4
+
5
+ module ActionView
6
+ class OutputFlow #:nodoc:
7
+ attr_reader :content
8
+
9
+ def initialize
10
+ @content = Hash.new { |h, k| h[k] = ActiveSupport::SafeBuffer.new }
11
+ end
12
+
13
+ # Called by _layout_for to read stored values.
14
+ def get(key)
15
+ @content[key]
16
+ end
17
+
18
+ # Called by each renderer object to set the layout contents.
19
+ def set(key, value)
20
+ @content[key] = ActiveSupport::SafeBuffer.new(value)
21
+ end
22
+
23
+ # Called by content_for
24
+ def append(key, value)
25
+ @content[key] << value
26
+ end
27
+ alias_method :append!, :append
28
+ end
29
+
30
+ class StreamingFlow < OutputFlow #:nodoc:
31
+ def initialize(view, fiber)
32
+ @view = view
33
+ @parent = nil
34
+ @child = view.output_buffer
35
+ @content = view.view_flow.content
36
+ @fiber = fiber
37
+ @root = Fiber.current.object_id
38
+ end
39
+
40
+ # Try to get stored content. If the content
41
+ # is not available and we're inside the layout fiber,
42
+ # then it will begin waiting for the given key and yield.
43
+ def get(key)
44
+ return super if @content.key?(key)
45
+
46
+ if inside_fiber?
47
+ view = @view
48
+
49
+ begin
50
+ @waiting_for = key
51
+ view.output_buffer, @parent = @child, view.output_buffer
52
+ Fiber.yield
53
+ ensure
54
+ @waiting_for = nil
55
+ view.output_buffer, @child = @parent, view.output_buffer
56
+ end
57
+ end
58
+
59
+ super
60
+ end
61
+
62
+ # Appends the contents for the given key. This is called
63
+ # by providing and resuming back to the fiber,
64
+ # if that's the key it's waiting for.
65
+ def append!(key, value)
66
+ super
67
+ @fiber.resume if @waiting_for == key
68
+ end
69
+
70
+ private
71
+
72
+ def inside_fiber?
73
+ Fiber.current.object_id != @root
74
+ end
75
+ end
76
+ end
@@ -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 = 6
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
+ end
17
+ end
@@ -0,0 +1,66 @@
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 :RenderingHelper
27
+ autoload :SanitizeHelper
28
+ autoload :TagHelper
29
+ autoload :TextHelper
30
+ autoload :TranslationHelper
31
+ autoload :UrlHelper
32
+ autoload :Tags
33
+
34
+ def self.eager_load!
35
+ super
36
+ Tags.eager_load!
37
+ end
38
+
39
+ extend ActiveSupport::Concern
40
+
41
+ include ActiveSupport::Benchmarkable
42
+ include ActiveModelHelper
43
+ include AssetTagHelper
44
+ include AssetUrlHelper
45
+ include AtomFeedHelper
46
+ include CacheHelper
47
+ include CaptureHelper
48
+ include ControllerHelper
49
+ include CspHelper
50
+ include CsrfHelper
51
+ include DateHelper
52
+ include DebugHelper
53
+ include FormHelper
54
+ include FormOptionsHelper
55
+ include FormTagHelper
56
+ include JavaScriptHelper
57
+ include NumberHelper
58
+ include OutputSafetyHelper
59
+ include RenderingHelper
60
+ include SanitizeHelper
61
+ include TagHelper
62
+ include TextHelper
63
+ include TranslationHelper
64
+ include UrlHelper
65
+ end
66
+ 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,488 @@
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", "", 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 blobs (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_to_limit: [100, 100]))
337
+ # # => <img src="/rails/active_storage/representations/.../tiger.jpg" />
338
+ # image_tag(user.avatar.variant(resize_to_limit: [100, 100]), size: '100')
339
+ # # => <img width="100" height="100" src="/rails/active_storage/representations/.../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 an HTML video tag for the +sources+. If +sources+ is a string,
359
+ # a single video tag will be returned. If +sources+ is an array, a video
360
+ # tag with nested source tags for each source will be returned. The
361
+ # +sources+ can be full paths or files that exist in your public videos
362
+ # directory.
363
+ #
364
+ # ==== Options
365
+ #
366
+ # When the last parameter is a hash you can add HTML attributes using that
367
+ # parameter. The following options are supported:
368
+ #
369
+ # * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
370
+ # before the video loads. The path is calculated like the +src+ of +image_tag+.
371
+ # * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
372
+ # width="30" and height="45", and "50" becomes width="50" and height="50".
373
+ # <tt>:size</tt> will be ignored if the value is not in the correct format.
374
+ # * <tt>:poster_skip_pipeline</tt> will bypass the asset pipeline when using
375
+ # the <tt>:poster</tt> option instead using an asset in the public folder.
376
+ #
377
+ # ==== Examples
378
+ #
379
+ # video_tag("trailer")
380
+ # # => <video src="/videos/trailer"></video>
381
+ # video_tag("trailer.ogg")
382
+ # # => <video src="/videos/trailer.ogg"></video>
383
+ # video_tag("trailer.ogg", controls: true, preload: 'none')
384
+ # # => <video preload="none" controls="controls" src="/videos/trailer.ogg"></video>
385
+ # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
386
+ # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video>
387
+ # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true)
388
+ # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="screenshot.png"></video>
389
+ # video_tag("/trailers/hd.avi", size: "16x16")
390
+ # # => <video src="/trailers/hd.avi" width="16" height="16"></video>
391
+ # video_tag("/trailers/hd.avi", size: "16")
392
+ # # => <video height="16" src="/trailers/hd.avi" width="16"></video>
393
+ # video_tag("/trailers/hd.avi", height: '32', width: '32')
394
+ # # => <video height="32" src="/trailers/hd.avi" width="32"></video>
395
+ # video_tag("trailer.ogg", "trailer.flv")
396
+ # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
397
+ # video_tag(["trailer.ogg", "trailer.flv"])
398
+ # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
399
+ # video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
400
+ # # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
401
+ def video_tag(*sources)
402
+ options = sources.extract_options!.symbolize_keys
403
+ public_poster_folder = options.delete(:poster_skip_pipeline)
404
+ sources << options
405
+ multiple_sources_tag_builder("video", sources) do |tag_options|
406
+ tag_options[:poster] = path_to_image(tag_options[:poster], skip_pipeline: public_poster_folder) if tag_options[:poster]
407
+ tag_options[:width], tag_options[:height] = extract_dimensions(tag_options.delete(:size)) if tag_options[:size]
408
+ end
409
+ end
410
+
411
+ # Returns an HTML audio tag for the +sources+. If +sources+ is a string,
412
+ # a single audio tag will be returned. If +sources+ is an array, an audio
413
+ # tag with nested source tags for each source will be returned. The
414
+ # +sources+ can be full paths or files that exist in your public audios
415
+ # directory.
416
+ #
417
+ # When the last parameter is a hash you can add HTML attributes using that
418
+ # parameter.
419
+ #
420
+ # audio_tag("sound")
421
+ # # => <audio src="/audios/sound"></audio>
422
+ # audio_tag("sound.wav")
423
+ # # => <audio src="/audios/sound.wav"></audio>
424
+ # audio_tag("sound.wav", autoplay: true, controls: true)
425
+ # # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio>
426
+ # audio_tag("sound.wav", "sound.mid")
427
+ # # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
428
+ def audio_tag(*sources)
429
+ multiple_sources_tag_builder("audio", sources)
430
+ end
431
+
432
+ private
433
+ def multiple_sources_tag_builder(type, sources)
434
+ options = sources.extract_options!.symbolize_keys
435
+ skip_pipeline = options.delete(:skip_pipeline)
436
+ sources.flatten!
437
+
438
+ yield options if block_given?
439
+
440
+ if sources.size > 1
441
+ content_tag(type, options) do
442
+ safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) }
443
+ end
444
+ else
445
+ options[:src] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline)
446
+ content_tag(type, nil, options)
447
+ end
448
+ end
449
+
450
+ def resolve_image_source(source, skip_pipeline)
451
+ if source.is_a?(Symbol) || source.is_a?(String)
452
+ path_to_image(source, skip_pipeline: skip_pipeline)
453
+ else
454
+ polymorphic_url(source)
455
+ end
456
+ rescue NoMethodError => e
457
+ raise ArgumentError, "Can't resolve image into URL: #{e}"
458
+ end
459
+
460
+ def extract_dimensions(size)
461
+ size = size.to_s
462
+ if /\A\d+x\d+\z/.match?(size)
463
+ size.split("x")
464
+ elsif /\A\d+\z/.match?(size)
465
+ [size, size]
466
+ end
467
+ end
468
+
469
+ def check_for_image_tag_errors(options)
470
+ if options[:size] && (options[:height] || options[:width])
471
+ raise ArgumentError, "Cannot pass a :size option with a :height or :width option"
472
+ end
473
+ end
474
+
475
+ def resolve_link_as(extname, mime_type)
476
+ if extname == "js"
477
+ "script"
478
+ elsif extname == "css"
479
+ "style"
480
+ elsif extname == "vtt"
481
+ "track"
482
+ elsif (type = mime_type.to_s.split("/")[0]) && type.in?(%w(audio video font))
483
+ type
484
+ end
485
+ end
486
+ end
487
+ end
488
+ end