view_component 4.0.0.alpha6 → 4.0.0.alpha7
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.
- checksums.yaml +4 -4
- data/app/controllers/concerns/view_component/preview_actions.rb +3 -3
- data/app/views/test_mailer/test_url_email.html.erb +1 -0
- data/docs/CHANGELOG.md +34 -0
- data/lib/view_component/base.rb +50 -56
- data/lib/view_component/compiler.rb +13 -3
- data/lib/view_component/config.rb +3 -9
- data/lib/view_component/configurable.rb +1 -1
- data/lib/view_component/engine.rb +4 -4
- data/lib/view_component/errors.rb +0 -12
- data/lib/view_component/inline_template.rb +1 -1
- data/lib/view_component/slotable.rb +44 -43
- data/lib/view_component/template.rb +3 -3
- data/lib/view_component/translatable.rb +7 -7
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component.rb +7 -5
- metadata +2 -2
- data/lib/view_component/use_helpers.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8be3a213c479ad3bc1cb82fad0a04b6b9a3f424a4995e0162e447c50cd6b0ef9
|
4
|
+
data.tar.gz: a7744949cc46b57628a55e78bb0bd766f048f140c8ca1769c271032e9d648176
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13776ebd6447652ce0ad682a40bc70a4141b6b2db8690c32d7191828adbe9fd16ec8d30775007b29ff4c71bc559317cc56b60c94ab2af67ef4d259ea89ec3220
|
7
|
+
data.tar.gz: 2463587d1336f2b30e17e51f728526157c27752adb66bbc3703d20b68326d20eb734ee2990c7583866723ba3d927564d22ea6ddd9c0a70e515264043ce1d08d6
|
@@ -8,7 +8,7 @@ module ViewComponent
|
|
8
8
|
prepend_view_path File.expand_path("../../../views", __dir__)
|
9
9
|
|
10
10
|
around_action :set_locale, only: :previews
|
11
|
-
before_action :require_local!, unless: :
|
11
|
+
before_action :require_local!, unless: :previews_enabled?
|
12
12
|
|
13
13
|
content_security_policy(false)
|
14
14
|
|
@@ -51,8 +51,8 @@ module ViewComponent
|
|
51
51
|
end
|
52
52
|
|
53
53
|
# :doc:
|
54
|
-
def
|
55
|
-
ViewComponent::Base.config.
|
54
|
+
def previews_enabled?
|
55
|
+
ViewComponent::Base.config.previews.enabled
|
56
56
|
end
|
57
57
|
|
58
58
|
# :doc:
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render(UrlForMailerComponent.new) %>
|
data/docs/CHANGELOG.md
CHANGED
@@ -10,6 +10,36 @@ nav_order: 6
|
|
10
10
|
|
11
11
|
## main
|
12
12
|
|
13
|
+
## 4.0.0.alpha7
|
14
|
+
|
15
|
+
* BREAKING: Remove deprecated `use_helper(s)`. Use `include MyHelper` or `helpers.` proxy instead.
|
16
|
+
|
17
|
+
*Joel Hawksley*
|
18
|
+
|
19
|
+
* BREAKING: Support compatibility with `Dry::Initializer`. As a result, `EmptyOrInvalidInitializerError` will no longer be raised.
|
20
|
+
|
21
|
+
*Joel Hawksley*
|
22
|
+
|
23
|
+
* BREAKING: Rename `config.generate.component_parent_class` to `config.generate.parent_class`.
|
24
|
+
|
25
|
+
*Joel Hawksley*
|
26
|
+
|
27
|
+
* Fix bug where `config.previews.enabled` did not function properly in production environments.
|
28
|
+
|
29
|
+
*Joel Hawksley*
|
30
|
+
|
31
|
+
* `config.previews.default_layout` should default to nil.
|
32
|
+
|
33
|
+
*Joel Hawksley*
|
34
|
+
|
35
|
+
* Add test case for absolute URL path helpers in mailers.
|
36
|
+
|
37
|
+
*Joel Hawksley*
|
38
|
+
|
39
|
+
* Fix bug where response format wasn't set, which caused issues with Turbo Frames.
|
40
|
+
|
41
|
+
*Joel Hawksley*
|
42
|
+
|
13
43
|
## 4.0.0.alpha6
|
14
44
|
|
15
45
|
* BREAKING: Remove `config.test_controller` in favor of `vc_test_controller_class` test helper method.
|
@@ -186,6 +216,10 @@ This release makes the following breaking changes:
|
|
186
216
|
|
187
217
|
*Simon Fish*
|
188
218
|
|
219
|
+
* Deprecate `use_helper(s)`. Use `include MyHelper` or `helpers.` proxy instead.
|
220
|
+
|
221
|
+
*Joel Hawksley*
|
222
|
+
|
189
223
|
* Reduce string allocations during compilation.
|
190
224
|
|
191
225
|
*Jonathan del Strother*
|
data/lib/view_component/base.rb
CHANGED
@@ -14,7 +14,6 @@ require "view_component/slotable"
|
|
14
14
|
require "view_component/template"
|
15
15
|
require "view_component/translatable"
|
16
16
|
require "view_component/with_content_helper"
|
17
|
-
require "view_component/use_helpers"
|
18
17
|
|
19
18
|
module ActionView
|
20
19
|
class OutputBuffer
|
@@ -48,11 +47,11 @@ module ViewComponent
|
|
48
47
|
end
|
49
48
|
|
50
49
|
include ActionView::Helpers
|
50
|
+
include Rails.application.routes.url_helpers if defined?(Rails) && Rails.application
|
51
51
|
include ERB::Escape
|
52
52
|
include ActiveSupport::CoreExt::ERBUtil
|
53
53
|
|
54
54
|
include ViewComponent::InlineTemplate
|
55
|
-
include ViewComponent::UseHelpers
|
56
55
|
include ViewComponent::Slotable
|
57
56
|
include ViewComponent::Translatable
|
58
57
|
include ViewComponent::WithContentHelper
|
@@ -70,8 +69,9 @@ module ViewComponent
|
|
70
69
|
delegate :content_security_policy_nonce, to: :helpers
|
71
70
|
|
72
71
|
# Config option that strips trailing whitespace in templates before compiling them.
|
73
|
-
class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false
|
74
|
-
|
72
|
+
class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false, default: false
|
73
|
+
|
74
|
+
class_attribute :__vc_response_format, instance_accessor: false, instance_predicate: false, default: nil
|
75
75
|
|
76
76
|
attr_accessor :__vc_original_view_context
|
77
77
|
attr_reader :current_template
|
@@ -91,6 +91,11 @@ module ViewComponent
|
|
91
91
|
|
92
92
|
using RequestDetails
|
93
93
|
|
94
|
+
# Including `Rails.application.routes.url_helpers` defines an initializer that accepts (...),
|
95
|
+
# so we have to define our own empty initializer to overwrite it.
|
96
|
+
def initialize
|
97
|
+
end
|
98
|
+
|
94
99
|
# Entrypoint for rendering components.
|
95
100
|
#
|
96
101
|
# - `view_context`: ActionView context from calling view
|
@@ -141,7 +146,7 @@ module ViewComponent
|
|
141
146
|
value = if output_preamble.blank? && output_postamble.blank?
|
142
147
|
rendered_template
|
143
148
|
else
|
144
|
-
|
149
|
+
__vc_safe_output_preamble + rendered_template + __vc_safe_output_postamble
|
145
150
|
end
|
146
151
|
end
|
147
152
|
|
@@ -334,6 +339,10 @@ module ViewComponent
|
|
334
339
|
__vc_render_in_block_provided? || __vc_content_set_by_with_content_defined?
|
335
340
|
end
|
336
341
|
|
342
|
+
def format
|
343
|
+
self.class.__vc_response_format
|
344
|
+
end
|
345
|
+
|
337
346
|
private
|
338
347
|
|
339
348
|
attr_reader :view_context
|
@@ -346,11 +355,7 @@ module ViewComponent
|
|
346
355
|
defined?(@__vc_content_set_by_with_content)
|
347
356
|
end
|
348
357
|
|
349
|
-
def
|
350
|
-
defined?(@__vc_content_evaluated) && @__vc_content_evaluated
|
351
|
-
end
|
352
|
-
|
353
|
-
def maybe_escape_html(text)
|
358
|
+
def __vc_maybe_escape_html(text)
|
354
359
|
return text if @current_template && !@current_template.html?
|
355
360
|
return text if text.blank?
|
356
361
|
|
@@ -362,14 +367,14 @@ module ViewComponent
|
|
362
367
|
end
|
363
368
|
end
|
364
369
|
|
365
|
-
def
|
366
|
-
|
370
|
+
def __vc_safe_output_preamble
|
371
|
+
__vc_maybe_escape_html(output_preamble) do
|
367
372
|
Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe preamble. The preamble will be automatically escaped, but you may want to investigate.")
|
368
373
|
end
|
369
374
|
end
|
370
375
|
|
371
|
-
def
|
372
|
-
|
376
|
+
def __vc_safe_output_postamble
|
377
|
+
__vc_maybe_escape_html(output_postamble) do
|
373
378
|
Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe postamble. The postamble will be automatically escaped, but you may want to investigate.")
|
374
379
|
end
|
375
380
|
end
|
@@ -432,12 +437,12 @@ module ViewComponent
|
|
432
437
|
#
|
433
438
|
# Defaults to `false`.
|
434
439
|
#
|
435
|
-
# ####
|
440
|
+
# #### ßparent_class
|
436
441
|
#
|
437
442
|
# Parent class for generated components
|
438
443
|
#
|
439
444
|
# ```ruby
|
440
|
-
# config.view_component.generate.
|
445
|
+
# config.view_component.generate.parent_class = "MyBaseComponent"
|
441
446
|
# ```
|
442
447
|
#
|
443
448
|
# Defaults to nil. If this is falsy, generators will use
|
@@ -511,6 +516,11 @@ module ViewComponent
|
|
511
516
|
Collection.new(self, collection, spacer_component, **args)
|
512
517
|
end
|
513
518
|
|
519
|
+
# @private
|
520
|
+
def __vc_compile(raise_errors: false, force: false)
|
521
|
+
__vc_compiler.compile(raise_errors: raise_errors, force: force)
|
522
|
+
end
|
523
|
+
|
514
524
|
# @private
|
515
525
|
def inherited(child)
|
516
526
|
# Compile so child will inherit compiled `call_*` template methods that
|
@@ -533,12 +543,6 @@ module ViewComponent
|
|
533
543
|
RUBY
|
534
544
|
end
|
535
545
|
|
536
|
-
# If Rails application is loaded, add application url_helpers to the component context
|
537
|
-
# we need to check this to use this gem as a dependency
|
538
|
-
if defined?(Rails) && Rails.application && !(child < Rails.application.routes.url_helpers)
|
539
|
-
child.include Rails.application.routes.url_helpers
|
540
|
-
end
|
541
|
-
|
542
546
|
# Derive the source location of the component Ruby file from the call stack.
|
543
547
|
# We need to ignore `inherited` frames here as they indicate that `inherited`
|
544
548
|
# has been re-defined by the consuming application, likely in ApplicationComponent.
|
@@ -548,7 +552,7 @@ module ViewComponent
|
|
548
552
|
child.virtual_path = child.name&.underscore
|
549
553
|
|
550
554
|
# Set collection parameter to the extended component
|
551
|
-
child.with_collection_parameter
|
555
|
+
child.with_collection_parameter(__vc_provided_collection_parameter)
|
552
556
|
|
553
557
|
if instance_methods(false).include?(:render_template_for)
|
554
558
|
vc_ancestor_calls = defined?(@__vc_ancestor_calls) ? @__vc_ancestor_calls.dup : []
|
@@ -570,11 +574,6 @@ module ViewComponent
|
|
570
574
|
__vc_compile unless __vc_compiled?
|
571
575
|
end
|
572
576
|
|
573
|
-
# @private
|
574
|
-
def __vc_compile(raise_errors: false, force: false)
|
575
|
-
__vc_compiler.compile(raise_errors: raise_errors, force: force)
|
576
|
-
end
|
577
|
-
|
578
577
|
# @private
|
579
578
|
def __vc_compiler
|
580
579
|
@__vc_compiler ||= Compiler.new(self)
|
@@ -588,8 +587,8 @@ module ViewComponent
|
|
588
587
|
#
|
589
588
|
# @param parameter [Symbol] The parameter name used when rendering elements of a collection.
|
590
589
|
def with_collection_parameter(parameter)
|
591
|
-
@
|
592
|
-
@
|
590
|
+
@__vc_provided_collection_parameter = parameter
|
591
|
+
@__vc_initialize_parameters = nil
|
593
592
|
end
|
594
593
|
|
595
594
|
# Strips trailing whitespace from templates before compiling them.
|
@@ -619,17 +618,10 @@ module ViewComponent
|
|
619
618
|
# rendering is optional.
|
620
619
|
# @private
|
621
620
|
def __vc_validate_collection_parameter!(validate_default: false)
|
622
|
-
parameter = validate_default ? __vc_collection_parameter :
|
621
|
+
parameter = validate_default ? __vc_collection_parameter : __vc_provided_collection_parameter
|
623
622
|
|
624
623
|
return unless parameter
|
625
|
-
return if
|
626
|
-
|
627
|
-
# If Ruby can't parse the component class, then the initialize
|
628
|
-
# parameters will be empty and ViewComponent will not be able to render
|
629
|
-
# the component.
|
630
|
-
if initialize_parameters.empty?
|
631
|
-
raise EmptyOrInvalidInitializerError.new(name, parameter)
|
632
|
-
end
|
624
|
+
return if __vc_initialize_parameter_names.include?(parameter) || __vc_splatted_keyword_argument_present?
|
633
625
|
|
634
626
|
raise MissingCollectionArgumentError.new(name, parameter)
|
635
627
|
end
|
@@ -639,55 +631,57 @@ module ViewComponent
|
|
639
631
|
# methods.
|
640
632
|
# @private
|
641
633
|
def __vc_validate_initialization_parameters!
|
642
|
-
return unless
|
634
|
+
return unless __vc_initialize_parameter_names.include?(:content)
|
643
635
|
|
644
636
|
raise ReservedParameterError.new(name, :content)
|
645
637
|
end
|
646
638
|
|
647
639
|
# @private
|
648
640
|
def __vc_collection_parameter
|
649
|
-
|
641
|
+
@__vc_provided_collection_parameter ||= name && name.demodulize.underscore.chomp("_component").to_sym
|
650
642
|
end
|
651
643
|
|
652
644
|
# @private
|
653
645
|
def __vc_collection_counter_parameter
|
654
|
-
:"#{__vc_collection_parameter}_counter"
|
646
|
+
@__vc_collection_counter_parameter ||= :"#{__vc_collection_parameter}_counter"
|
655
647
|
end
|
656
648
|
|
657
649
|
# @private
|
658
650
|
def __vc_counter_argument_present?
|
659
|
-
|
651
|
+
__vc_initialize_parameter_names.include?(__vc_collection_counter_parameter)
|
660
652
|
end
|
661
653
|
|
662
654
|
# @private
|
663
655
|
def __vc_collection_iteration_parameter
|
664
|
-
:"#{__vc_collection_parameter}_iteration"
|
656
|
+
@__vc_collection_iteration_parameter ||= :"#{__vc_collection_parameter}_iteration"
|
665
657
|
end
|
666
658
|
|
667
659
|
# @private
|
668
660
|
def __vc_iteration_argument_present?
|
669
|
-
|
661
|
+
__vc_initialize_parameter_names.include?(__vc_collection_iteration_parameter)
|
670
662
|
end
|
671
663
|
|
672
664
|
private
|
673
665
|
|
674
|
-
def
|
675
|
-
|
676
|
-
!initialize_parameters.include?([:keyrest, :**]) # Un-named splatted keyword args don't count!
|
666
|
+
def __vc_splatted_keyword_argument_present?
|
667
|
+
__vc_initialize_parameters.flatten.include?(:keyrest)
|
677
668
|
end
|
678
669
|
|
679
|
-
def
|
680
|
-
|
681
|
-
|
682
|
-
|
670
|
+
def __vc_initialize_parameter_names
|
671
|
+
@__vc_initialize_parameter_names ||=
|
672
|
+
if respond_to?(:attribute_names)
|
673
|
+
attribute_names.map(&:to_sym)
|
674
|
+
else
|
675
|
+
__vc_initialize_parameters.map(&:last)
|
676
|
+
end
|
683
677
|
end
|
684
678
|
|
685
|
-
def
|
686
|
-
@
|
679
|
+
def __vc_initialize_parameters
|
680
|
+
@__vc_initialize_parameters ||= instance_method(:initialize).parameters
|
687
681
|
end
|
688
682
|
|
689
|
-
def
|
690
|
-
@
|
683
|
+
def __vc_provided_collection_parameter
|
684
|
+
@__vc_provided_collection_parameter ||= nil
|
691
685
|
end
|
692
686
|
end
|
693
687
|
|
@@ -48,7 +48,16 @@ module ViewComponent
|
|
48
48
|
|
49
49
|
define_render_template_for
|
50
50
|
|
51
|
-
|
51
|
+
# Set the format if the component only responds to a single format.
|
52
|
+
# Unfortunately we cannot determine which format a multi-format
|
53
|
+
# component will respond to until render time, so those components
|
54
|
+
# will not set the response format.
|
55
|
+
#
|
56
|
+
# TODO: Investigate upstream changes necessary to support multi-format renderables
|
57
|
+
unique_formats = templates.map(&:format).uniq
|
58
|
+
@component.__vc_response_format = unique_formats.last if unique_formats.one?
|
59
|
+
|
60
|
+
@component.__vc_register_default_slots
|
52
61
|
@component.__vc_build_i18n_backend
|
53
62
|
|
54
63
|
CompileCache.register(@component)
|
@@ -109,6 +118,7 @@ module ViewComponent
|
|
109
118
|
errors << "Couldn't find a template file or inline render method for #{@component}." if @templates.empty?
|
110
119
|
|
111
120
|
@templates
|
121
|
+
.reject { |template| template.inline_call? && !template.defined_on_self? }
|
112
122
|
.map { |template| [template.variant, template.format] }
|
113
123
|
.tally
|
114
124
|
.select { |_, count| count > 1 }
|
@@ -167,10 +177,10 @@ module ViewComponent
|
|
167
177
|
|
168
178
|
def gather_templates
|
169
179
|
@templates ||=
|
170
|
-
if @component.
|
180
|
+
if @component.__vc_inline_template.present?
|
171
181
|
[Template::Inline.new(
|
172
182
|
component: @component,
|
173
|
-
inline_template: @component.
|
183
|
+
inline_template: @component.__vc_inline_template
|
174
184
|
)]
|
175
185
|
else
|
176
186
|
path_parser = ActionView::Resolver::PathParser.new
|
@@ -118,9 +118,9 @@ module ViewComponent
|
|
118
118
|
#
|
119
119
|
# #### `#default_layout`
|
120
120
|
#
|
121
|
-
# A custom default layout used for the previews index page and individual previews. Defaults to `
|
121
|
+
# A custom default layout used for the previews index page and individual previews. Defaults to `nil`:
|
122
122
|
#
|
123
|
-
# config.view_component.previews.default_layout =
|
123
|
+
# config.view_component.previews.default_layout = "preview_layout"
|
124
124
|
#
|
125
125
|
|
126
126
|
# @!attribute instrumentation_enabled
|
@@ -128,12 +128,6 @@ module ViewComponent
|
|
128
128
|
# Whether ActiveSupport notifications are enabled.
|
129
129
|
# Defaults to `false`.
|
130
130
|
|
131
|
-
# @!attribute test_controller
|
132
|
-
# @return [String]
|
133
|
-
# The controller used for testing components.
|
134
|
-
# Can also be configured on a per-test basis using `#with_controller_class`.
|
135
|
-
# Defaults to `ApplicationController`.
|
136
|
-
|
137
131
|
def default_preview_paths
|
138
132
|
(default_rails_preview_paths + default_rails_engines_preview_paths).uniq
|
139
133
|
end
|
@@ -170,7 +164,7 @@ module ViewComponent
|
|
170
164
|
options.controller = "ViewComponentsController"
|
171
165
|
options.route = "/rails/view_components"
|
172
166
|
options.enabled = Rails.env.development? || Rails.env.test?
|
173
|
-
options.default_layout =
|
167
|
+
options.default_layout = nil
|
174
168
|
options.paths = default_preview_paths
|
175
169
|
options
|
176
170
|
end
|
@@ -5,7 +5,7 @@ module ViewComponent
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
next if respond_to?(:config) && config.respond_to?(:view_component) && config.respond_to_missing?(:
|
8
|
+
next if respond_to?(:config) && config.respond_to?(:view_component) && config.respond_to_missing?(:instrumentation_enabled)
|
9
9
|
|
10
10
|
include ActiveSupport::Configurable
|
11
11
|
|
@@ -15,9 +15,9 @@ module ViewComponent
|
|
15
15
|
options[config_option] ||= ViewComponent::Base.public_send(config_option)
|
16
16
|
end
|
17
17
|
options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
|
18
|
-
options.
|
18
|
+
options.previews.enabled = (Rails.env.development? || Rails.env.test?) if options.previews.enabled.nil?
|
19
19
|
|
20
|
-
if options.
|
20
|
+
if options.previews.enabled
|
21
21
|
# This is still necessary because when `config.view_component` is declared, `Rails.root` is unspecified.
|
22
22
|
options.previews.paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?(
|
23
23
|
"#{Rails.root}/test/components/previews"
|
@@ -36,7 +36,7 @@ module ViewComponent
|
|
36
36
|
initializer "view_component.set_autoload_paths" do |app|
|
37
37
|
options = app.config.view_component
|
38
38
|
|
39
|
-
if options.
|
39
|
+
if options.previews.enabled && !options.previews.paths.empty?
|
40
40
|
paths_to_add = options.previews.paths - ActiveSupport::Dependencies.autoload_paths
|
41
41
|
ActiveSupport::Dependencies.autoload_paths.concat(paths_to_add) if paths_to_add.any?
|
42
42
|
end
|
@@ -88,7 +88,7 @@ module ViewComponent
|
|
88
88
|
config.after_initialize do |app|
|
89
89
|
options = app.config.view_component
|
90
90
|
|
91
|
-
if options.
|
91
|
+
if options.previews.enabled
|
92
92
|
app.routes.prepend do
|
93
93
|
preview_controller = options.previews.controller.sub(/Controller$/, "").underscore
|
94
94
|
|
@@ -65,18 +65,6 @@ module ViewComponent
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
class EmptyOrInvalidInitializerError < StandardError
|
69
|
-
MESSAGE =
|
70
|
-
"The COMPONENT initializer is empty or invalid. " \
|
71
|
-
"It must accept the parameter `PARAMETER` to render it as a collection.\n\n" \
|
72
|
-
"To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
|
73
|
-
"See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."
|
74
|
-
|
75
|
-
def initialize(klass_name, parameter)
|
76
|
-
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
68
|
class MissingCollectionArgumentError < StandardError
|
81
69
|
MESSAGE =
|
82
70
|
"The initializer for COMPONENT doesn't accept the parameter `PARAMETER`, " \
|
@@ -73,25 +73,25 @@ module ViewComponent
|
|
73
73
|
#
|
74
74
|
# <%= render_inline(MyComponent.new.with_header_content("Foo")) %>
|
75
75
|
def renders_one(slot_name, callable = nil)
|
76
|
-
|
76
|
+
__vc_validate_singular_slot_name(slot_name)
|
77
77
|
|
78
78
|
if callable.is_a?(Hash) && callable.key?(:types)
|
79
|
-
|
79
|
+
__vc_register_polymorphic_slot(slot_name, callable[:types], collection: false)
|
80
80
|
else
|
81
|
-
|
81
|
+
__vc_validate_plural_slot_name(ActiveSupport::Inflector.pluralize(slot_name).to_sym)
|
82
82
|
|
83
83
|
setter_method_name = :"with_#{slot_name}"
|
84
84
|
|
85
85
|
define_method setter_method_name do |*args, **kwargs, &block|
|
86
|
-
|
86
|
+
__vc_set_slot(slot_name, nil, *args, **kwargs, &block)
|
87
87
|
end
|
88
88
|
|
89
89
|
self::GeneratedSlotMethods.define_method slot_name do
|
90
|
-
|
90
|
+
__vc_get_slot(slot_name)
|
91
91
|
end
|
92
92
|
|
93
93
|
self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
|
94
|
-
|
94
|
+
__vc_get_slot(slot_name).present?
|
95
95
|
end
|
96
96
|
|
97
97
|
define_method :"with_#{slot_name}_content" do |content|
|
@@ -100,7 +100,7 @@ module ViewComponent
|
|
100
100
|
self
|
101
101
|
end
|
102
102
|
|
103
|
-
|
103
|
+
__vc_register_slot(slot_name, collection: false, callable: callable)
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
@@ -142,18 +142,18 @@ module ViewComponent
|
|
142
142
|
# <% end %>
|
143
143
|
# <% end %>
|
144
144
|
def renders_many(slot_name, callable = nil)
|
145
|
-
|
145
|
+
__vc_validate_plural_slot_name(slot_name)
|
146
146
|
|
147
147
|
if callable.is_a?(Hash) && callable.key?(:types)
|
148
|
-
|
148
|
+
__vc_register_polymorphic_slot(slot_name, callable[:types], collection: true)
|
149
149
|
else
|
150
150
|
singular_name = ActiveSupport::Inflector.singularize(slot_name)
|
151
|
-
|
151
|
+
__vc_validate_singular_slot_name(ActiveSupport::Inflector.singularize(slot_name).to_sym)
|
152
152
|
|
153
153
|
setter_method_name = :"with_#{singular_name}"
|
154
154
|
|
155
155
|
define_method setter_method_name do |*args, **kwargs, &block|
|
156
|
-
|
156
|
+
__vc_set_slot(slot_name, nil, *args, **kwargs, &block)
|
157
157
|
end
|
158
158
|
|
159
159
|
define_method :"with_#{singular_name}_content" do |content|
|
@@ -165,22 +165,22 @@ module ViewComponent
|
|
165
165
|
define_method :"with_#{slot_name}" do |collection_args = nil, &block|
|
166
166
|
collection_args.map do |args|
|
167
167
|
if args.respond_to?(:to_hash)
|
168
|
-
|
168
|
+
__vc_set_slot(slot_name, nil, **args, &block)
|
169
169
|
else
|
170
|
-
|
170
|
+
__vc_set_slot(slot_name, nil, *args, &block)
|
171
171
|
end
|
172
172
|
end
|
173
173
|
end
|
174
174
|
|
175
175
|
self::GeneratedSlotMethods.define_method slot_name do
|
176
|
-
|
176
|
+
__vc_get_slot(slot_name)
|
177
177
|
end
|
178
178
|
|
179
179
|
self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
|
180
|
-
|
180
|
+
__vc_get_slot(slot_name).present?
|
181
181
|
end
|
182
182
|
|
183
|
-
|
183
|
+
__vc_register_slot(slot_name, collection: true, callable: callable)
|
184
184
|
end
|
185
185
|
end
|
186
186
|
|
@@ -211,8 +211,9 @@ module ViewComponent
|
|
211
211
|
super
|
212
212
|
end
|
213
213
|
|
214
|
+
# @private
|
214
215
|
# Called by the compiler, as instance methods are not defined when slots are first registered
|
215
|
-
def
|
216
|
+
def __vc_register_default_slots
|
216
217
|
registered_slots.each do |slot_name, config|
|
217
218
|
default_method_name = :"default_#{slot_name}"
|
218
219
|
config[:default_method] = instance_methods.find { |method_name| method_name == default_method_name }
|
@@ -223,17 +224,17 @@ module ViewComponent
|
|
223
224
|
|
224
225
|
private
|
225
226
|
|
226
|
-
def
|
227
|
-
registered_slots[slot_name] =
|
227
|
+
def __vc_register_slot(slot_name, **kwargs)
|
228
|
+
registered_slots[slot_name] = __vc_define_slot(slot_name, **kwargs)
|
228
229
|
end
|
229
230
|
|
230
|
-
def
|
231
|
+
def __vc_register_polymorphic_slot(slot_name, types, collection:)
|
231
232
|
self::GeneratedSlotMethods.define_method(slot_name) do
|
232
|
-
|
233
|
+
__vc_get_slot(slot_name)
|
233
234
|
end
|
234
235
|
|
235
236
|
self::GeneratedSlotMethods.define_method(:"#{slot_name}?") do
|
236
|
-
|
237
|
+
__vc_get_slot(slot_name).present?
|
237
238
|
end
|
238
239
|
|
239
240
|
renderable_hash = types.each_with_object({}) do |(poly_type, poly_attributes_or_callable), memo|
|
@@ -252,7 +253,7 @@ module ViewComponent
|
|
252
253
|
"#{slot_name}_#{poly_type}"
|
253
254
|
end
|
254
255
|
|
255
|
-
memo[poly_type] =
|
256
|
+
memo[poly_type] = __vc_define_slot(
|
256
257
|
poly_slot_name, collection: collection, callable: poly_callable
|
257
258
|
)
|
258
259
|
|
@@ -263,7 +264,7 @@ module ViewComponent
|
|
263
264
|
end
|
264
265
|
|
265
266
|
define_method(setter_method_name) do |*args, **kwargs, &block|
|
266
|
-
|
267
|
+
__vc_set_polymorphic_slot(slot_name, poly_type, *args, **kwargs, &block)
|
267
268
|
end
|
268
269
|
|
269
270
|
define_method :"with_#{poly_slot_name}_content" do |content|
|
@@ -279,7 +280,7 @@ module ViewComponent
|
|
279
280
|
}
|
280
281
|
end
|
281
282
|
|
282
|
-
def
|
283
|
+
def __vc_define_slot(slot_name, collection:, callable:)
|
283
284
|
slot = {collection: collection}
|
284
285
|
return slot unless callable
|
285
286
|
|
@@ -302,18 +303,18 @@ module ViewComponent
|
|
302
303
|
slot
|
303
304
|
end
|
304
305
|
|
305
|
-
def
|
306
|
+
def __vc_validate_plural_slot_name(slot_name)
|
306
307
|
if RESERVED_NAMES[:plural].include?(slot_name.to_sym)
|
307
308
|
raise ReservedPluralSlotNameError.new(name, slot_name)
|
308
309
|
end
|
309
310
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
311
|
+
__vc_raise_if_slot_name_uncountable(slot_name)
|
312
|
+
__vc_raise_if_slot_conflicts_with_call(slot_name)
|
313
|
+
__vc_raise_if_slot_ends_with_question_mark(slot_name)
|
314
|
+
__vc_raise_if_slot_registered(slot_name)
|
314
315
|
end
|
315
316
|
|
316
|
-
def
|
317
|
+
def __vc_validate_singular_slot_name(slot_name)
|
317
318
|
if slot_name.to_sym == :content
|
318
319
|
raise ContentSlotNameError.new(name)
|
319
320
|
end
|
@@ -322,28 +323,28 @@ module ViewComponent
|
|
322
323
|
raise ReservedSingularSlotNameError.new(name, slot_name)
|
323
324
|
end
|
324
325
|
|
325
|
-
|
326
|
-
|
327
|
-
|
326
|
+
__vc_raise_if_slot_conflicts_with_call(slot_name)
|
327
|
+
__vc_raise_if_slot_ends_with_question_mark(slot_name)
|
328
|
+
__vc_raise_if_slot_registered(slot_name)
|
328
329
|
end
|
329
330
|
|
330
|
-
def
|
331
|
+
def __vc_raise_if_slot_registered(slot_name)
|
331
332
|
if registered_slots.key?(slot_name)
|
332
333
|
raise RedefinedSlotError.new(name, slot_name)
|
333
334
|
end
|
334
335
|
end
|
335
336
|
|
336
|
-
def
|
337
|
+
def __vc_raise_if_slot_ends_with_question_mark(slot_name)
|
337
338
|
raise SlotPredicateNameError.new(name, slot_name) if slot_name.to_s.end_with?("?")
|
338
339
|
end
|
339
340
|
|
340
|
-
def
|
341
|
+
def __vc_raise_if_slot_conflicts_with_call(slot_name)
|
341
342
|
if slot_name.start_with?("call_")
|
342
343
|
raise InvalidSlotNameError, "Slot cannot start with 'call_'. Please rename #{slot_name}"
|
343
344
|
end
|
344
345
|
end
|
345
346
|
|
346
|
-
def
|
347
|
+
def __vc_raise_if_slot_name_uncountable(slot_name)
|
347
348
|
slot_name = slot_name.to_s
|
348
349
|
if slot_name.pluralize == slot_name.singularize
|
349
350
|
raise UncountableSlotNameError.new(name, slot_name)
|
@@ -351,9 +352,9 @@ module ViewComponent
|
|
351
352
|
end
|
352
353
|
end
|
353
354
|
|
354
|
-
def
|
355
|
+
def __vc_get_slot(slot_name)
|
355
356
|
@__vc_set_slots ||= {}
|
356
|
-
content unless
|
357
|
+
content unless defined?(@__vc_content_evaluated) && @__vc_content_evaluated # ensure content is loaded so slots will be defined
|
357
358
|
|
358
359
|
# If the slot is set, return it
|
359
360
|
return @__vc_set_slots[slot_name] if @__vc_set_slots[slot_name]
|
@@ -376,7 +377,7 @@ module ViewComponent
|
|
376
377
|
end
|
377
378
|
end
|
378
379
|
|
379
|
-
def
|
380
|
+
def __vc_set_slot(slot_name, slot_definition = nil, *args, **kwargs, &block)
|
380
381
|
slot_definition ||= self.class.registered_slots[slot_name]
|
381
382
|
slot = Slot.new(self)
|
382
383
|
|
@@ -433,7 +434,7 @@ module ViewComponent
|
|
433
434
|
slot
|
434
435
|
end
|
435
436
|
|
436
|
-
def
|
437
|
+
def __vc_set_polymorphic_slot(slot_name, poly_type = nil, *args, **kwargs, &block)
|
437
438
|
slot_definition = self.class.registered_slots[slot_name]
|
438
439
|
|
439
440
|
if !slot_definition[:collection] && defined?(@__vc_set_slots) && @__vc_set_slots[slot_name]
|
@@ -442,7 +443,7 @@ module ViewComponent
|
|
442
443
|
|
443
444
|
poly_def = slot_definition[:renderable_hash][poly_type]
|
444
445
|
|
445
|
-
|
446
|
+
__vc_set_slot(slot_name, poly_def, *args, **kwargs, &block)
|
446
447
|
end
|
447
448
|
end
|
448
449
|
end
|
@@ -43,7 +43,7 @@ module ViewComponent
|
|
43
43
|
attr_reader :source
|
44
44
|
|
45
45
|
def initialize(component:, inline_template:)
|
46
|
-
details = ActionView::TemplateDetails.new(nil, inline_template.language.to_sym,
|
46
|
+
details = ActionView::TemplateDetails.new(nil, inline_template.language.to_sym, DEFAULT_FORMAT, nil)
|
47
47
|
|
48
48
|
super(
|
49
49
|
component: component,
|
@@ -63,7 +63,7 @@ module ViewComponent
|
|
63
63
|
class InlineCall < Template
|
64
64
|
def initialize(component:, method_name:, defined_on_self:)
|
65
65
|
variant = method_name.to_s.include?("call_") ? method_name.to_s.sub("call_", "").to_sym : nil
|
66
|
-
details = ActionView::TemplateDetails.new(nil, nil,
|
66
|
+
details = ActionView::TemplateDetails.new(nil, nil, DEFAULT_FORMAT, variant)
|
67
67
|
|
68
68
|
super(component: component, details: details)
|
69
69
|
|
@@ -82,7 +82,7 @@ module ViewComponent
|
|
82
82
|
def safe_method_name_call
|
83
83
|
m = safe_method_name
|
84
84
|
proc do
|
85
|
-
|
85
|
+
__vc_maybe_escape_html(send(m)) do
|
86
86
|
Kernel.warn("WARNING: The #{self.class} component rendered HTML-unsafe output. " \
|
87
87
|
"The output will be automatically escaped, but you may want to investigate.")
|
88
88
|
end
|
@@ -70,12 +70,12 @@ module ViewComponent
|
|
70
70
|
|
71
71
|
def initialize(scope:, load_paths:)
|
72
72
|
@__vc_i18n_scope = scope.split(".").map(&:to_sym)
|
73
|
-
@
|
73
|
+
@__vc_load_paths = load_paths
|
74
74
|
end
|
75
75
|
|
76
76
|
# Ensure the Simple backend won't load paths from ::I18n.load_path
|
77
77
|
def load_translations
|
78
|
-
super(@
|
78
|
+
super(@__vc_load_paths)
|
79
79
|
end
|
80
80
|
|
81
81
|
def scope_data(data)
|
@@ -100,7 +100,7 @@ module ViewComponent
|
|
100
100
|
key = self.class.__vc_i18n_key(key, options.delete(:scope))
|
101
101
|
as_html = HTML_SAFE_TRANSLATION_KEY.match?(key)
|
102
102
|
|
103
|
-
|
103
|
+
__vc_html_escape_translation_options!(options) if as_html
|
104
104
|
|
105
105
|
if key.start_with?(__vc_i18n_scope + ".")
|
106
106
|
translated =
|
@@ -113,7 +113,7 @@ module ViewComponent
|
|
113
113
|
return @view_context.translate(key, locale: locale, **options)
|
114
114
|
end
|
115
115
|
|
116
|
-
translated =
|
116
|
+
translated = __vc_html_safe_translation(translated) if as_html
|
117
117
|
translated
|
118
118
|
else
|
119
119
|
@view_context.translate(key, locale: locale, **options)
|
@@ -127,9 +127,9 @@ module ViewComponent
|
|
127
127
|
|
128
128
|
private
|
129
129
|
|
130
|
-
def
|
130
|
+
def __vc_html_safe_translation(translation)
|
131
131
|
if translation.respond_to?(:map)
|
132
|
-
translation.map { |element|
|
132
|
+
translation.map { |element| __vc_html_safe_translation(element) }
|
133
133
|
else
|
134
134
|
# It's assumed here that objects loaded by the i18n backend will respond to `#html_safe?`.
|
135
135
|
# It's reasonable that if we're in Rails, `active_support/core_ext/string/output_safety.rb`
|
@@ -138,7 +138,7 @@ module ViewComponent
|
|
138
138
|
end
|
139
139
|
end
|
140
140
|
|
141
|
-
def
|
141
|
+
def __vc_html_escape_translation_options!(options)
|
142
142
|
options.except(*::I18n::RESERVED_KEYS).each do |name, value|
|
143
143
|
next if name == :count && value.is_a?(Numeric)
|
144
144
|
|
data/lib/view_component.rb
CHANGED
@@ -7,7 +7,6 @@ module ViewComponent
|
|
7
7
|
extend ActiveSupport::Autoload
|
8
8
|
|
9
9
|
autoload :Base
|
10
|
-
autoload :CaptureCompatibility
|
11
10
|
autoload :Compiler
|
12
11
|
autoload :CompileCache
|
13
12
|
autoload :Config
|
@@ -15,11 +14,14 @@ module ViewComponent
|
|
15
14
|
autoload :InlineTemplate
|
16
15
|
autoload :Instrumentation
|
17
16
|
autoload :Preview
|
18
|
-
autoload :TestHelpers
|
19
|
-
autoload :SystemTestHelpers
|
20
|
-
autoload :TestCase
|
21
|
-
autoload :SystemTestCase
|
22
17
|
autoload :Translatable
|
18
|
+
|
19
|
+
if Rails.env.test?
|
20
|
+
autoload :TestHelpers
|
21
|
+
autoload :SystemTestHelpers
|
22
|
+
autoload :TestCase
|
23
|
+
autoload :SystemTestCase
|
24
|
+
end
|
23
25
|
end
|
24
26
|
|
25
27
|
require "view_component/engine" if defined?(Rails::Engine)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: view_component
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0.0.
|
4
|
+
version: 4.0.0.alpha7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ViewComponent Team
|
@@ -54,6 +54,7 @@ files:
|
|
54
54
|
- app/controllers/view_components_system_test_controller.rb
|
55
55
|
- app/views/test_mailer/test_asset_email.html.erb
|
56
56
|
- app/views/test_mailer/test_email.html.erb
|
57
|
+
- app/views/test_mailer/test_url_email.html.erb
|
57
58
|
- app/views/view_components/index.html.erb
|
58
59
|
- app/views/view_components/preview.html.erb
|
59
60
|
- app/views/view_components/previews.html.erb
|
@@ -80,7 +81,6 @@ files:
|
|
80
81
|
- lib/view_component/test_case.rb
|
81
82
|
- lib/view_component/test_helpers.rb
|
82
83
|
- lib/view_component/translatable.rb
|
83
|
-
- lib/view_component/use_helpers.rb
|
84
84
|
- lib/view_component/version.rb
|
85
85
|
- lib/view_component/with_content_helper.rb
|
86
86
|
homepage: https://viewcomponent.org
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ViewComponent::UseHelpers
|
4
|
-
extend ActiveSupport::Concern
|
5
|
-
|
6
|
-
class_methods do
|
7
|
-
def use_helpers(*args, from: nil, prefix: false)
|
8
|
-
args.each { |helper_method| use_helper(helper_method, from: from, prefix: prefix) }
|
9
|
-
end
|
10
|
-
|
11
|
-
def use_helper(helper_method, from: nil, prefix: false)
|
12
|
-
helper_method_name = full_helper_method_name(helper_method, prefix: prefix, source: from)
|
13
|
-
|
14
|
-
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
15
|
-
def #{helper_method_name}(...)
|
16
|
-
raise HelpersCalledBeforeRenderError if view_context.nil?
|
17
|
-
|
18
|
-
#{define_helper(helper_method: helper_method, source: from)}
|
19
|
-
end
|
20
|
-
RUBY
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def full_helper_method_name(helper_method, prefix: false, source: nil)
|
26
|
-
return helper_method unless prefix.present?
|
27
|
-
|
28
|
-
if !!prefix == prefix
|
29
|
-
"#{source.to_s.underscore}_#{helper_method}"
|
30
|
-
else
|
31
|
-
"#{prefix}_#{helper_method}"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def define_helper(helper_method:, source:)
|
36
|
-
return "__vc_original_view_context.#{helper_method}(...)" unless source.present?
|
37
|
-
|
38
|
-
"#{source}.instance_method(:#{helper_method}).bind(self).call(...)"
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|