brut 0.0.13 → 0.0.20
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/Gemfile.lock +4 -6
- data/brut.gemspec +1 -3
- data/lib/brut/back_end.rb +7 -0
- data/lib/brut/cli/apps/scaffold.rb +16 -24
- data/lib/brut/framework/config.rb +4 -43
- data/lib/brut/framework/mcp.rb +1 -1
- data/lib/brut/front_end/asset_path_resolver.rb +15 -0
- data/lib/brut/front_end/component.rb +66 -234
- data/lib/brut/front_end/components/constraint_violations.rb +9 -9
- data/lib/brut/front_end/components/form_tag.rb +16 -28
- data/lib/brut/front_end/components/i18n_translations.rb +12 -13
- data/lib/brut/front_end/components/input.rb +0 -1
- data/lib/brut/front_end/components/inputs/csrf_token.rb +2 -2
- data/lib/brut/front_end/components/inputs/radio_button.rb +6 -6
- data/lib/brut/front_end/components/inputs/select.rb +13 -20
- data/lib/brut/front_end/components/inputs/text_field.rb +18 -33
- data/lib/brut/front_end/components/inputs/textarea.rb +11 -26
- data/lib/brut/front_end/components/locale_detection.rb +2 -2
- data/lib/brut/front_end/components/page_identifier.rb +3 -5
- data/lib/brut/front_end/components/{time.rb → time_tag.rb} +13 -10
- data/lib/brut/front_end/components/traceparent.rb +5 -6
- data/lib/brut/front_end/http_method.rb +4 -0
- data/lib/brut/front_end/inline_svg_locator.rb +21 -0
- data/lib/brut/front_end/layout.rb +3 -0
- data/lib/brut/front_end/page.rb +16 -29
- data/lib/brut/front_end/request_context.rb +13 -0
- data/lib/brut/front_end/routing.rb +3 -2
- data/lib/brut/front_end.rb +41 -0
- data/lib/brut/i18n/base_methods.rb +14 -8
- data/lib/brut/i18n/for_back_end.rb +5 -0
- data/lib/brut/i18n/for_cli.rb +2 -1
- data/lib/brut/i18n/for_html.rb +9 -1
- data/lib/brut/i18n.rb +1 -0
- data/lib/brut/sinatra_helpers.rb +12 -7
- data/lib/brut/spec_support/component_support.rb +9 -9
- data/lib/brut/spec_support/e2e_support.rb +4 -0
- data/lib/brut/spec_support/matchers/have_i18n_string.rb +5 -0
- data/lib/brut/spec_support/rspec_setup.rb +1 -0
- data/lib/brut/spec_support.rb +4 -3
- data/lib/brut/version.rb +1 -1
- data/lib/brut.rb +2 -46
- metadata +13 -41
- data/lib/brut/front_end/template.rb +0 -47
- data/lib/brut/front_end/templates/block_filter.rb +0 -61
- data/lib/brut/front_end/templates/erb_engine.rb +0 -26
- data/lib/brut/front_end/templates/erb_parser.rb +0 -84
- data/lib/brut/front_end/templates/escapable_filter.rb +0 -20
- data/lib/brut/front_end/templates/html_safe_string.rb +0 -68
- data/lib/brut/front_end/templates/locator.rb +0 -60
@@ -31,27 +31,27 @@ class Brut::FrontEnd::Components::ConstraintViolations < Brut::FrontEnd::Compone
|
|
31
31
|
@input_name = input_name
|
32
32
|
@array = !index.nil?
|
33
33
|
@index = index || 0
|
34
|
-
@html_attributes = html_attributes
|
35
|
-
@message_html_attributes = message_html_attributes
|
34
|
+
@html_attributes = html_attributes.map {|name,value| [ name.to_sym, value ] }.to_h
|
35
|
+
@message_html_attributes = message_html_attributes.map {|name,value| [ name.to_sym, value ] }.to_h
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
38
|
+
def view_template
|
39
39
|
html_attributes = {
|
40
|
-
"input-name": @array ? "#{@input_name}[]" : @input_name
|
40
|
+
"input-name": @array ? "#{@input_name}[]" : @input_name.to_s,
|
41
41
|
}.merge(@html_attributes)
|
42
42
|
|
43
43
|
message_html_attributes = {
|
44
44
|
"server-side": true,
|
45
45
|
}.merge(@message_html_attributes)
|
46
46
|
|
47
|
-
|
47
|
+
brut_cv_messages(**html_attributes) do
|
48
48
|
@form.input(@input_name, index: @index).validity_state.select { |constraint|
|
49
49
|
!constraint.client_side?
|
50
|
-
}.
|
51
|
-
|
52
|
-
t("cv.be.#{constraint}", **constraint.context)
|
50
|
+
}.each do |constraint|
|
51
|
+
brut_cv(**message_html_attributes) do
|
52
|
+
t("cv.be.#{constraint}", **constraint.context)
|
53
53
|
end
|
54
|
-
|
54
|
+
end
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require "rexml"
|
2
1
|
# Represents a `<form>` HTML element that includes a CSRF token as needed. You likely want to use this class via the {Brut::FrontEnd::Component::Helpers#form_tag} method.
|
3
2
|
class Brut::FrontEnd::Components::FormTag < Brut::FrontEnd::Component
|
4
3
|
# Creates the form surrounding the contents of the block yielded to it. If the form's action is a POST, it will include a CSRF token.
|
@@ -22,7 +21,7 @@ class Brut::FrontEnd::Components::FormTag < Brut::FrontEnd::Component
|
|
22
21
|
# @option html_attributes [Class|Brut::FrontEnd::Form] :for the form object or class representing this HTML form *or* the class of a handler the form should submit to. If you pass this, you may not pass the HTML attributes `:action` or `:method`. Both will be derived from this object.
|
23
22
|
# @option html_attributes [String] «any-other-key» attributes to set on the `<form>` tag
|
24
23
|
# @yield No parameters given. This is expected to return additional markup to appear inside the `<form>` element.
|
25
|
-
def initialize(route_params: {}, **html_attributes
|
24
|
+
def initialize(route_params: {}, **html_attributes)
|
26
25
|
form_class = html_attributes.delete(:for) # Cannot be a keyword arg, since for is a reserved word
|
27
26
|
if !form_class.nil?
|
28
27
|
if form_class.kind_of?(Brut::FrontEnd::Form)
|
@@ -49,34 +48,23 @@ class Brut::FrontEnd::Components::FormTag < Brut::FrontEnd::Component
|
|
49
48
|
|
50
49
|
@include_csrf_token = http_method.post?
|
51
50
|
@csrf_token_omit_reasoning = http_method.get? ? "because this form's action is a GET" : nil
|
52
|
-
@attributes = html_attributes
|
53
|
-
@contents = contents
|
51
|
+
@attributes = html_attributes.merge(method: http_method)
|
54
52
|
end
|
55
53
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
else
|
65
|
-
REXML::Attribute.new(key,value).to_string
|
54
|
+
def view_template
|
55
|
+
form(**@attributes) do
|
56
|
+
if @include_csrf_token
|
57
|
+
render Brut::FrontEnd::RequestContext.inject(Brut::FrontEnd::Components::Inputs::CsrfToken)
|
58
|
+
elsif Brut.container.project_env.development?
|
59
|
+
comment do
|
60
|
+
"CSRF Token omitted #{@csrf_token_omit_reasoning} (this message only appears in development)"
|
61
|
+
end
|
66
62
|
end
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
html_safe!("<!-- CSRF Token omitted #{@csrf_token_omit_reasoning} (this message only appears in development) -->")
|
72
|
-
else
|
73
|
-
""
|
74
|
-
end
|
75
|
-
%{
|
76
|
-
<form #{attribute_string}>
|
77
|
-
#{ csrf_token_component }
|
78
|
-
#{ @contents.() }
|
79
|
-
</form>
|
80
|
-
}
|
63
|
+
if block_given?
|
64
|
+
yield
|
65
|
+
end
|
66
|
+
end
|
81
67
|
end
|
68
|
+
|
69
|
+
|
82
70
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require "rexml"
|
2
|
-
|
3
1
|
# Produces `<brut-i18n-translation>` entries for the given values. This is used for client-side constraint violation messaging with
|
4
2
|
# JavaScript. The `<brut-constraint-violation-message>` tag uses these keys to produce messages on the client.
|
5
3
|
#
|
@@ -39,13 +37,13 @@ class Brut::FrontEnd::Components::I18nTranslations < Brut::FrontEnd::Component
|
|
39
37
|
end
|
40
38
|
|
41
39
|
# @!visibility private
|
42
|
-
def
|
40
|
+
def view_template
|
43
41
|
values = ::I18n.t(@i18n_key_root)
|
44
42
|
if values.kind_of?(String)
|
45
43
|
values = { "" => values }
|
46
44
|
end
|
47
45
|
|
48
|
-
values.
|
46
|
+
values.each do |key,value|
|
49
47
|
if !value.kind_of?(String)
|
50
48
|
raise "Key #{key} under #{@i18n_key_root} maps to a #{value.class} instead of a String. For #{self.class} to work, the value must be a String"
|
51
49
|
end
|
@@ -54,16 +52,17 @@ class Brut::FrontEnd::Components::I18nTranslations < Brut::FrontEnd::Component
|
|
54
52
|
else
|
55
53
|
"#{@i18n_key_root}.#{key}"
|
56
54
|
end
|
57
|
-
attributes =
|
58
|
-
|
59
|
-
|
60
|
-
|
55
|
+
attributes = {
|
56
|
+
key: i18n_key,
|
57
|
+
value: value.to_s,
|
58
|
+
}
|
61
59
|
if !Brut.container.project_env.production?
|
62
|
-
attributes
|
63
|
-
attributes
|
60
|
+
attributes[:show_warnings] = true
|
61
|
+
attributes[:id] = "brut-18n-#{key}"
|
64
62
|
end
|
65
|
-
|
66
|
-
|
67
|
-
|
63
|
+
|
64
|
+
brut_i18n_translation(**attributes)
|
65
|
+
|
66
|
+
end
|
68
67
|
end
|
69
68
|
end
|
@@ -4,7 +4,7 @@ class Brut::FrontEnd::Components::Inputs::CsrfToken < Brut::FrontEnd::Components
|
|
4
4
|
def initialize(csrf_token:)
|
5
5
|
@csrf_token = csrf_token
|
6
6
|
end
|
7
|
-
def
|
8
|
-
|
7
|
+
def view_template
|
8
|
+
input(type: "hidden", name: "authenticity_token", value: @csrf_token)
|
9
9
|
end
|
10
10
|
end
|
@@ -14,18 +14,18 @@ class Brut::FrontEnd::Components::Inputs::RadioButton < Brut::FrontEnd::Componen
|
|
14
14
|
# @param [Hash] html_attributes any additional HTML attributes to include on the `<input>` element.
|
15
15
|
def self.for_form_input(form:, input_name:, value:, html_attributes: {})
|
16
16
|
default_html_attributes = {}
|
17
|
-
html_attributes = html_attributes.map { |key,value| [ key.
|
17
|
+
html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
|
18
18
|
input = form.input(input_name)
|
19
19
|
|
20
|
-
default_html_attributes[
|
21
|
-
default_html_attributes[
|
22
|
-
default_html_attributes[
|
23
|
-
default_html_attributes[
|
20
|
+
default_html_attributes[:required] = input.required
|
21
|
+
default_html_attributes[:type] = "radio"
|
22
|
+
default_html_attributes[:name] = input.name
|
23
|
+
default_html_attributes[:value] = value
|
24
24
|
|
25
25
|
selected_value = input.value
|
26
26
|
|
27
27
|
if selected_value == value
|
28
|
-
default_html_attributes[
|
28
|
+
default_html_attributes[:checked] = true
|
29
29
|
end
|
30
30
|
|
31
31
|
if !form.new? && !input.valid?
|
@@ -21,10 +21,11 @@ class Brut::FrontEnd::Components::Inputs::Select < Brut::FrontEnd::Components::I
|
|
21
21
|
option_text_attribute:,
|
22
22
|
index: nil,
|
23
23
|
html_attributes: {})
|
24
|
+
html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
|
24
25
|
default_html_attributes = {}
|
25
26
|
index ||= 0
|
26
27
|
input = form.input(input_name, index:)
|
27
|
-
default_html_attributes[
|
28
|
+
default_html_attributes[:required] = input.required
|
28
29
|
if !form.new? && !input.valid?
|
29
30
|
default_html_attributes["data-invalid"] = true
|
30
31
|
input.validity_state.each do |constraint,violated|
|
@@ -61,36 +62,28 @@ class Brut::FrontEnd::Components::Inputs::Select < Brut::FrontEnd::Components::I
|
|
61
62
|
@selected_value = selected_value
|
62
63
|
@value_attribute = value_attribute
|
63
64
|
@option_text_attribute = option_text_attribute
|
65
|
+
@html_attributes = html_attributes
|
64
66
|
|
65
|
-
html_attributes[
|
66
|
-
@sanitized_attributes = html_attributes.map { |key,value|
|
67
|
-
[
|
68
|
-
key.to_s.gsub(/[\s\"\'>\/=]/,"-"),
|
69
|
-
value
|
70
|
-
]
|
71
|
-
}.select { |key,value|
|
72
|
-
!value.nil?
|
73
|
-
}.to_h
|
67
|
+
@html_attributes[:name] = name
|
74
68
|
end
|
75
69
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
70
|
+
def view_template
|
71
|
+
select(**@html_attributes) {
|
72
|
+
if @include_blank
|
73
|
+
option(**@include_blank.option_attributes) {
|
74
|
+
@include_blank.text_content
|
75
|
+
}
|
76
|
+
end
|
77
|
+
options = @options.each do |option|
|
79
78
|
value = option.send(@value_attribute)
|
80
79
|
option_attributes = { value: value }
|
81
80
|
if value == @selected_value
|
82
81
|
option_attributes[:selected] = true
|
83
82
|
end
|
84
|
-
|
83
|
+
option(**option_attributes) {
|
85
84
|
option.send(@option_text_attribute)
|
86
85
|
}
|
87
|
-
}
|
88
|
-
if @include_blank
|
89
|
-
options.unshift(html_tag(:option,**@include_blank.option_attributes) {
|
90
|
-
@include_blank.text_content
|
91
|
-
})
|
92
86
|
end
|
93
|
-
options.join("\n")
|
94
87
|
}
|
95
88
|
end
|
96
89
|
private
|
@@ -9,40 +9,39 @@ class Brut::FrontEnd::Components::Inputs::TextField < Brut::FrontEnd::Components
|
|
9
9
|
# @param [Hash] html_attributes any additional HTML attributes to include on the `<input>` element.
|
10
10
|
def self.for_form_input(form:, input_name:, index: nil, html_attributes: {})
|
11
11
|
default_html_attributes = {}
|
12
|
-
html_attributes = html_attributes.map { |key,value| [ key.
|
12
|
+
html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
|
13
13
|
input = form.input(input_name, index:)
|
14
14
|
|
15
|
-
default_html_attributes[
|
16
|
-
default_html_attributes[
|
17
|
-
default_html_attributes[
|
18
|
-
default_html_attributes[
|
15
|
+
default_html_attributes[:required] = input.required
|
16
|
+
default_html_attributes[:pattern] = input.pattern
|
17
|
+
default_html_attributes[:type] = input.type
|
18
|
+
default_html_attributes[:name] = if input.array?
|
19
19
|
"#{input.name}[]"
|
20
20
|
else
|
21
21
|
input.name
|
22
22
|
end
|
23
23
|
|
24
24
|
if input.max
|
25
|
-
default_html_attributes[
|
25
|
+
default_html_attributes[:max] = input.max
|
26
26
|
end
|
27
27
|
if input.maxlength
|
28
|
-
default_html_attributes[
|
28
|
+
default_html_attributes[:maxlength] = input.maxlength
|
29
29
|
end
|
30
30
|
if input.min
|
31
|
-
default_html_attributes[
|
31
|
+
default_html_attributes[:min] = input.min
|
32
32
|
end
|
33
33
|
if input.minlength
|
34
|
-
default_html_attributes[
|
35
|
-
end
|
34
|
+
default_html_attributes[:minlength] = input.minlength end
|
36
35
|
if input.step
|
37
|
-
default_html_attributes[
|
36
|
+
default_html_attributes[:step] = input.step
|
38
37
|
end
|
39
38
|
value = input.value
|
40
39
|
|
41
40
|
if input.type == "checkbox"
|
42
|
-
default_html_attributes[
|
43
|
-
default_html_attributes[
|
41
|
+
default_html_attributes[:value] = (index || true).to_s
|
42
|
+
default_html_attributes[:checked] = value == "true"
|
44
43
|
else
|
45
|
-
default_html_attributes[
|
44
|
+
default_html_attributes[:value] = value.to_s
|
46
45
|
end
|
47
46
|
if !form.new? && !input.valid?
|
48
47
|
default_html_attributes["data-invalid"] = true
|
@@ -55,30 +54,16 @@ class Brut::FrontEnd::Components::Inputs::TextField < Brut::FrontEnd::Components
|
|
55
54
|
Brut::FrontEnd::Components::Inputs::TextField.new(default_html_attributes.merge(html_attributes))
|
56
55
|
end
|
57
56
|
|
57
|
+
def invalid? = @attributes["data-invalid"] == true
|
58
|
+
|
58
59
|
# Create an instance
|
59
60
|
#
|
60
61
|
# @param [Hash] attributes HTML attributes to put on the element.
|
61
62
|
def initialize(attributes)
|
62
|
-
@
|
63
|
-
[
|
64
|
-
key.to_s.gsub(/[\s\"\'>\/=]/,"-"),
|
65
|
-
value
|
66
|
-
]
|
67
|
-
}.select { |key,value|
|
68
|
-
!value.nil?
|
69
|
-
}.to_h
|
63
|
+
@attributes = attributes
|
70
64
|
end
|
71
65
|
|
72
|
-
def
|
73
|
-
|
74
|
-
if value == true
|
75
|
-
key
|
76
|
-
elsif value == false
|
77
|
-
""
|
78
|
-
else
|
79
|
-
REXML::Attribute.new(key,value).to_string
|
80
|
-
end
|
81
|
-
}.join(" ")
|
82
|
-
"<input #{attribute_string}>"
|
66
|
+
def view_template
|
67
|
+
input(**@attributes)
|
83
68
|
end
|
84
69
|
end
|
@@ -8,22 +8,23 @@ class Brut::FrontEnd::Components::Inputs::Textarea < Brut::FrontEnd::Components:
|
|
8
8
|
# @param [Integer] index if this input is part of an array, this is the index into that array. This is used to get the input's value.
|
9
9
|
# @param [Hash] html_attributes any additional HTML attributes to include on the `<textarea>` element.
|
10
10
|
def self.for_form_input(form:, input_name:, index: nil, html_attributes: {})
|
11
|
+
html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
|
11
12
|
default_html_attributes = {}
|
12
13
|
|
13
14
|
index ||= 0
|
14
15
|
input = form.input(input_name, index:)
|
15
16
|
|
16
|
-
default_html_attributes[
|
17
|
-
default_html_attributes[
|
17
|
+
default_html_attributes[:required] = input.required
|
18
|
+
default_html_attributes[:name] = if input.array?
|
18
19
|
"#{input.name}[]"
|
19
20
|
else
|
20
21
|
input.name
|
21
22
|
end
|
22
23
|
if input.maxlength
|
23
|
-
default_html_attributes[
|
24
|
+
default_html_attributes[:maxlength] = input.maxlength
|
24
25
|
end
|
25
26
|
if input.minlength
|
26
|
-
default_html_attributes[
|
27
|
+
default_html_attributes[:minlength] = input.minlength
|
27
28
|
end
|
28
29
|
if !form.new? && !input.valid?
|
29
30
|
default_html_attributes["data-invalid"] = true
|
@@ -41,31 +42,15 @@ class Brut::FrontEnd::Components::Inputs::Textarea < Brut::FrontEnd::Components:
|
|
41
42
|
# @param [Hash] attributes HTML attributes to put on the element.
|
42
43
|
# @param [String] value the value to place inside the text area
|
43
44
|
def initialize(attributes, value)
|
44
|
-
@
|
45
|
-
|
46
|
-
key.to_s.gsub(/[\s\"\'>\/=]/,"-"),
|
47
|
-
value
|
48
|
-
]
|
49
|
-
}.select { |key,value|
|
50
|
-
!value.nil?
|
51
|
-
}.to_h
|
52
|
-
@value = value
|
45
|
+
@attributes = attributes
|
46
|
+
@value = value
|
53
47
|
end
|
54
48
|
|
55
|
-
def
|
49
|
+
def invalid? = @attributes["data-invalid"] == true
|
56
50
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
key
|
61
|
-
elsif value == false
|
62
|
-
""
|
63
|
-
else
|
64
|
-
REXML::Attribute.new(key,value).to_string
|
65
|
-
end
|
66
|
-
}.join(" ")
|
67
|
-
%{
|
68
|
-
<textarea #{attribute_string}>#{ @value }</textarea>
|
51
|
+
def view_template
|
52
|
+
textarea(**@attributes) {
|
53
|
+
@value
|
69
54
|
}
|
70
55
|
end
|
71
56
|
end
|
@@ -13,7 +13,7 @@ class Brut::FrontEnd::Components::LocaleDetection < Brut::FrontEnd::Component
|
|
13
13
|
@url = Brut::FrontEnd::Handlers::LocaleDetectionHandler.routing
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
16
|
+
def view_template
|
17
17
|
attributes = {
|
18
18
|
"url" => @url,
|
19
19
|
}
|
@@ -27,6 +27,6 @@ class Brut::FrontEnd::Components::LocaleDetection < Brut::FrontEnd::Component
|
|
27
27
|
attributes["show-warnings"] = true
|
28
28
|
end
|
29
29
|
|
30
|
-
|
30
|
+
brut_locale_detection(**attributes)
|
31
31
|
end
|
32
32
|
end
|
@@ -1,15 +1,13 @@
|
|
1
|
-
require "rexml"
|
2
|
-
|
3
1
|
# Renders a `<meta>` tag that contains the name of the page. This is useful for end to end tests to assert that they are on a specific page before continuing with the test. It can eliminate a lot of confusion when a test fails.
|
4
2
|
class Brut::FrontEnd::Components::PageIdentifier < Brut::FrontEnd::Component
|
5
3
|
def initialize(page_name)
|
6
4
|
@page_name = page_name
|
7
5
|
end
|
8
6
|
|
9
|
-
def
|
7
|
+
def view_template
|
10
8
|
if Brut.container.project_env.production?
|
11
|
-
return
|
9
|
+
return nil
|
12
10
|
end
|
13
|
-
|
11
|
+
meta(name: "class", content: @page_name)
|
14
12
|
end
|
15
13
|
end
|
@@ -1,6 +1,5 @@
|
|
1
|
-
require "rexml"
|
2
1
|
# Renders a date or timestamp accessibly, using the `<time>` element. Likely you will use this via the {Brut::FrontEnd::Component::Helpers#time_tag} method. This will account for the current request's time zone. See {Clock}.
|
3
|
-
class Brut::FrontEnd::Components::
|
2
|
+
class Brut::FrontEnd::Components::TimeTag < Brut::FrontEnd::Component
|
4
3
|
include Brut::I18n::ForHTML
|
5
4
|
# Creates the component
|
6
5
|
# @param timestamp [Time] the timestamp you wish to render. Mutually exclusive with `date`.
|
@@ -21,13 +20,18 @@ class Brut::FrontEnd::Components::Time < Brut::FrontEnd::Component
|
|
21
20
|
skip_year_if_same: true,
|
22
21
|
skip_dow_if_not_this_week: true,
|
23
22
|
attribute_format: :iso_8601,
|
24
|
-
|
25
|
-
|
23
|
+
clock: :from_request_context,
|
24
|
+
**only_contains_class
|
26
25
|
)
|
27
26
|
require_exactly_one!(timestamp:,date:)
|
28
27
|
|
29
28
|
@date_only = timestamp.nil?
|
30
29
|
@timestamp = timestamp || date
|
30
|
+
@clock = if clock == :from_request_context
|
31
|
+
Brut::FrontEnd::RequestContext.current[:clock]
|
32
|
+
else
|
33
|
+
clock
|
34
|
+
end
|
31
35
|
|
32
36
|
formats = [ format ]
|
33
37
|
use_no_year = skip_year_if_same && @timestamp.year == Time.now.year
|
@@ -69,21 +73,20 @@ class Brut::FrontEnd::Components::Time < Brut::FrontEnd::Component
|
|
69
73
|
@format = found_format.to_sym
|
70
74
|
@attribute_format = attribute_format.to_sym
|
71
75
|
@class_attribute = only_contains_class[:class] || ""
|
72
|
-
@contents = contents
|
73
76
|
end
|
74
77
|
|
75
|
-
def
|
78
|
+
def view_template
|
76
79
|
adjusted_value = if @date_only
|
77
80
|
@timestamp
|
78
81
|
else
|
79
|
-
clock.in_time_zone(@timestamp)
|
82
|
+
@clock.in_time_zone(@timestamp)
|
80
83
|
end
|
81
84
|
|
82
85
|
datetime_attribute = ::I18n.l(adjusted_value,format: @attribute_format)
|
83
86
|
|
84
|
-
|
85
|
-
if
|
86
|
-
|
87
|
+
time(class: @class_attribute, datetime: datetime_attribute) do
|
88
|
+
if block_given?
|
89
|
+
yield
|
87
90
|
else
|
88
91
|
::I18n.l(adjusted_value,format: @format)
|
89
92
|
end
|
@@ -8,15 +8,14 @@ class Brut::FrontEnd::Components::Traceparent < Brut::FrontEnd::Component
|
|
8
8
|
@traceparent = carrier["traceparent"]
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
11
|
+
def view_template
|
12
12
|
attributes = {
|
13
|
-
name: "traceparent"
|
13
|
+
name: "traceparent",
|
14
|
+
content: @traceparent,
|
14
15
|
}
|
15
|
-
if
|
16
|
-
attributes[:content] = @traceparent
|
17
|
-
else
|
16
|
+
if !@traceparent
|
18
17
|
attributes["data-no-traceparent"] = "no traceparent was available - this component may have been rendered outside of an existing OpenTelemetry context"
|
19
18
|
end
|
20
|
-
|
19
|
+
meta(**attributes)
|
21
20
|
end
|
22
21
|
end
|
@@ -1,5 +1,9 @@
|
|
1
|
+
require "phlex"
|
2
|
+
|
1
3
|
# Wrapper around an HTTP Method, ensuring it contains only a valid value.
|
2
4
|
class Brut::FrontEnd::HttpMethod
|
5
|
+
include Phlex::SGML::SafeObject
|
6
|
+
|
3
7
|
# Create an HTTP method from a string.
|
4
8
|
#
|
5
9
|
# @param [String|Symbol] string a string containing an HTTP method name. Case insensitive, and can be a symbol.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Brut::FrontEnd::InlineSvgLocator
|
2
|
+
def initialize(paths:)
|
3
|
+
@paths = Array(paths).map { |path| Pathname(path) }
|
4
|
+
end
|
5
|
+
|
6
|
+
def locate(base_name)
|
7
|
+
paths_to_try = @paths.map { |path|
|
8
|
+
path / "#{base_name}.svg"
|
9
|
+
}
|
10
|
+
paths_found = paths_to_try.select { |path|
|
11
|
+
path.exist?
|
12
|
+
}
|
13
|
+
if paths_found.empty?
|
14
|
+
raise "Could not locate SVG for #{base_name}. Tried: #{paths_to_try.map(&:to_s).join(', ')}"
|
15
|
+
end
|
16
|
+
if paths_found.length > 1
|
17
|
+
raise "Found more than one valid path for #{base_name}. You must rename your files to disambiguate them. These paths were all found: #{paths_found.map(&:to_s).join(', ')}"
|
18
|
+
end
|
19
|
+
return paths_found[0]
|
20
|
+
end
|
21
|
+
end
|
data/lib/brut/front_end/page.rb
CHANGED
@@ -14,7 +14,6 @@
|
|
14
14
|
# @see Brut::FrontEnd::Component
|
15
15
|
class Brut::FrontEnd::Page < Brut::FrontEnd::Component
|
16
16
|
include Brut::FrontEnd::HandlingResults
|
17
|
-
using Brut::FrontEnd::Templates::HTMLSafeString::Refinement
|
18
17
|
|
19
18
|
# Returns the name of the layout for this page. This string is used to find an ERB file in `app/src/front_end/layouts`. Every page
|
20
19
|
# must have a layout. If you wish to render a page with no layout, create an empty layout in your app and use that.
|
@@ -31,43 +30,35 @@ class Brut::FrontEnd::Page < Brut::FrontEnd::Component
|
|
31
30
|
# @return [URI|Brut::FrontEnd::HttpStatus|Object] If you return a `URI` (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#redirect_to}), the user is redirected and {#render} is never called. If you return a {Brut::FrontEnd::HttpStatus} (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#http_status}), {#render} is skipped and that status is returned with no content. If anything else is returned, {#render} is called as normal.
|
32
31
|
def before_render = nil
|
33
32
|
|
34
|
-
|
33
|
+
def with_layout(&block)
|
34
|
+
layout_class = Module.const_get(
|
35
|
+
layout_class = RichString.new([
|
36
|
+
self.layout,
|
37
|
+
"layout"
|
38
|
+
].join("_")).camelize
|
39
|
+
)
|
40
|
+
render layout_class.new(page_name:,&block)
|
41
|
+
end
|
42
|
+
|
43
|
+
|
35
44
|
def handle!
|
36
45
|
case before_render
|
37
46
|
in URI => uri
|
38
|
-
Brut.container.instrumentation.add_event("before_render got a URI", uri: uri)
|
39
47
|
uri
|
40
48
|
in Brut::FrontEnd::HttpStatus => http_status
|
41
|
-
Brut.container.instrumentation.add_event("before_render got status", http_status: http_status)
|
42
49
|
http_status
|
43
50
|
else
|
44
|
-
|
51
|
+
self.call
|
45
52
|
end
|
46
53
|
end
|
47
54
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
# Also, generally don't override this unles you need to do something unusual. Overriding this will completely bypass the layout
|
53
|
-
# system and skip all ERB processing. Unlike {Brut::FrontEnd::Component#render}, overriding this method does not provide access to injected data from the request context.
|
54
|
-
#
|
55
|
-
# @return [Brut::FrontEnd::Templates::HTMLSafeString] string containing the page's full HTML.
|
56
|
-
def render
|
57
|
-
layout_template = Brut.container.layout_locator.locate(self.layout).
|
58
|
-
then { |layout_erb_file| Brut::FrontEnd::Template.new(layout_erb_file) }
|
59
|
-
|
60
|
-
template = Brut.container.page_locator.locate(self.template_name).
|
61
|
-
then { |erb_file| Brut::FrontEnd::Template.new(erb_file) }
|
62
|
-
|
63
|
-
Brut.container.instrumentation.add_event("templates found", layout: layout_template.template_file_path, page: template.template_file_path)
|
64
|
-
|
65
|
-
page = template.render_template(self).html_safe!
|
66
|
-
layout_template.render_template(self) do
|
67
|
-
page
|
55
|
+
def view_template
|
56
|
+
with_layout do
|
57
|
+
page_template
|
68
58
|
end
|
69
59
|
end
|
70
60
|
|
61
|
+
|
71
62
|
# @return [String] name of this page for use in debugging or for whatever reason you may want to dynamically refer to the page's name. The default value is the class name.
|
72
63
|
def self.page_name = self.name
|
73
64
|
|
@@ -77,10 +68,6 @@ class Brut::FrontEnd::Page < Brut::FrontEnd::Component
|
|
77
68
|
# @!visibility private
|
78
69
|
def component_name = raise Brut::Framework::Errors::Bug,"#{self.class} is not a component"
|
79
70
|
|
80
|
-
private
|
81
|
-
|
82
|
-
def template_name = RichString.new(self.class.name).underscorized.to_s.gsub(/^pages\//,"")
|
83
|
-
|
84
71
|
end
|
85
72
|
|
86
73
|
# Holds pages included with the Brut framework
|