phlex 1.9.2 → 1.10.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of phlex might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.rubocop.yml +12 -0
- data/.ruby-version +1 -1
- data/.solargraph.yml +11 -0
- data/CHANGELOG.md +16 -1
- data/Gemfile +12 -8
- data/README.md +3 -1
- data/SECURITY.md +7 -6
- data/config/sus.rb +0 -2
- data/fixtures/components/say_hi.rb +15 -0
- data/fixtures/components.rb +7 -0
- data/fixtures/layout.rb +1 -1
- data/fixtures/page.rb +19 -17
- data/gd/phlex/sgml/attributes.rb +30 -2
- data/lib/phlex/black_hole.rb +1 -1
- data/lib/phlex/context.rb +45 -8
- data/lib/phlex/csv.rb +133 -0
- data/lib/phlex/deferred_render.rb +1 -1
- data/lib/phlex/elements.rb +72 -19
- data/lib/phlex/helpers.rb +21 -5
- data/lib/phlex/html/standard_elements.rb +138 -96
- data/lib/phlex/html/void_elements.rb +17 -17
- data/lib/phlex/html.rb +16 -2
- data/lib/phlex/kit.rb +62 -0
- data/lib/phlex/sgml.rb +108 -60
- data/lib/phlex/svg/standard_elements.rb +64 -64
- data/lib/phlex/svg.rb +10 -0
- data/lib/phlex/version.rb +1 -1
- data/lib/phlex.rb +34 -12
- metadata +8 -46
- data/lib/phlex/overrides/symbol/name.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf84b97b1bea4f96f14b9cb01c649c12bc838fcf2e2f8c305e7d6b5ea26bb78e
|
4
|
+
data.tar.gz: 1a310b4f74440e8131ebc8ae7847f1c9fb6b371d3015c67f2da373ebf75ee038
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e245d5943e46753a1ce044e620252e03353e5ffec1da5a21a158ad7c4ff2080b2fc47388b6af7da73db94a434ece93bb1c119a6498fc11291127e6e0b51aa85
|
7
|
+
data.tar.gz: 8772d302dbbe246b7200886403c3f2dbb7129b54f729b7cac4e13809cbdf5b9896aa6cabc23ae7b1b09df41c79ef8ec0b74be925f8cd2ca75fe726cdd7fc9011
|
data/.rubocop.yml
CHANGED
@@ -13,3 +13,15 @@ Style/MixinUsage:
|
|
13
13
|
|
14
14
|
Style/RedundantDoubleSplatHashBraces:
|
15
15
|
Enabled: false
|
16
|
+
|
17
|
+
Style/OptionalArguments:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Naming/AsciiIdentifiers:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Naming/MethodName:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Style/ReturnNilInPredicateMethodDefinition:
|
27
|
+
Enabled: false
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.3.0
|
data/.solargraph.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,21 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
4
4
|
|
5
|
+
## [1.10.0] 2024-04-05
|
6
|
+
|
7
|
+
- [Added] new `Phlex::CSV` class for streaming CSV views
|
8
|
+
- [Added] new (experimental) `Phlex::Kit` for collections of components
|
9
|
+
- [Added] support for selective rendering
|
10
|
+
- [Changed] `mix` does a better job when mixing different types of attributes
|
11
|
+
- [Changed] Phlex will now try to call `to_s` on attribute values
|
12
|
+
- [Changed] No runtime dependencies
|
13
|
+
- [Deprecated] `Phlex::HTML#param`, (`<param>`) tags have been deprecated
|
14
|
+
- [Deprecated] Defining the `template` method is now deprecated. You should define `view_template` instead. In Phlex 2.0, the `template` method will render a `<template>` tag.
|
15
|
+
|
16
|
+
# [1.9.1] 2024-03-11
|
17
|
+
|
18
|
+
- Security update
|
19
|
+
|
5
20
|
## [1.9.0] 2024-11-24
|
6
21
|
|
7
22
|
- Improved documentation
|
@@ -42,6 +57,6 @@ All notable changes to this project will be documented in this file. The format
|
|
42
57
|
- Removed the `menuitem` element as it's a deprecated HTML element.
|
43
58
|
- Removed the `SGML#text` method. This has been replaced with `SGML#plain`.
|
44
59
|
|
45
|
-
|
60
|
+
---
|
46
61
|
|
47
62
|
Before this changelog was introduced, changes were logged in the [release notes](https://github.com/phlex-ruby/phlex/releases).
|
data/Gemfile
CHANGED
@@ -5,13 +5,17 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
|
5
5
|
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
gem "rubocop"
|
9
|
-
gem "sus"
|
10
|
-
gem "benchmark-ips"
|
11
|
-
gem "yard"
|
12
|
-
# gem "green_dots", github: "joeldrapper/green_dots"
|
13
|
-
|
14
8
|
group :test do
|
15
|
-
gem "
|
16
|
-
|
9
|
+
gem "sus"
|
10
|
+
if RUBY_ENGINE == "ruby" && RUBY_VERSION[0] > "3"
|
11
|
+
gem "async"
|
12
|
+
end
|
13
|
+
gem "concurrent-ruby"
|
14
|
+
end
|
15
|
+
|
16
|
+
group :development do
|
17
|
+
gem "rubocop"
|
18
|
+
gem "solargraph"
|
19
|
+
gem "yard"
|
20
|
+
gem "benchmark-ips"
|
17
21
|
end
|
data/README.md
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
<a href="https://www.phlex.fun"><img alt="Phlex logo" src="https://www.phlex.fun/assets/logo.png" width="180" /></a>
|
1
|
+
<a href="https://www.phlex.fun/"><img alt="Phlex logo" src="https://www.phlex.fun/assets/logo.png" width="180" /></a>
|
2
2
|
|
3
3
|
Phlex lets you compose web views in pure Ruby — kind of like JSX, but not really anything like JSX. It’s super-fast, thread-safe and supports TruffleRuby v22.2+, JRuby v9.2+ and MRI v2.7+. Phlex currently supports [HTML](https://rubydoc.info/gems/phlex/Phlex/HTML) and [SVG](https://rubydoc.info/gems/phlex/Phlex/SVG) views, and we’re exploring JSON and XML.
|
4
4
|
|
5
|
+
Docs and more at [Phlex.fun](https://www.phlex.fun/)
|
6
|
+
|
5
7
|
### Prior Art 🎨
|
6
8
|
|
7
9
|
- [markaby](https://github.com/markaby/markaby)
|
data/SECURITY.md
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
-
# Security
|
1
|
+
# Security
|
2
2
|
|
3
|
-
|
3
|
+
If you find a possible security vulnerability, please [send us a private advisory](https://github.com/phlex-ruby/phlex/security/advisories/new).
|
4
4
|
|
5
|
-
|
5
|
+
> [!WARNING]
|
6
|
+
> Please do not open a public Issue or Pull Request.
|
6
7
|
|
7
8
|
## Bug bounty
|
8
9
|
|
9
|
-
[
|
10
|
+
There is currently a bounty of $100 USD, kindly sponsored by [Seth Horsley](https://twitter.com/SethHorsley), for the next serious vulnerability responsibly disclosed to us.
|
10
11
|
|
11
|
-
##
|
12
|
+
## Bug bounty pot
|
12
13
|
|
13
|
-
If you wish to sponsor a bug bounty for Phlex, please get in touch with Joel at joel@drapper.me.
|
14
|
+
If you wish to sponsor a bug bounty for Phlex, please get in touch with Joel at [joel@drapper.me](mailto:joel@drapper.me).
|
data/config/sus.rb
CHANGED
data/fixtures/layout.rb
CHANGED
data/fixtures/page.rb
CHANGED
@@ -2,30 +2,32 @@
|
|
2
2
|
|
3
3
|
module Example
|
4
4
|
class Page < Phlex::HTML
|
5
|
-
def
|
5
|
+
def view_template
|
6
6
|
render LayoutComponent.new do
|
7
7
|
h1 { "Hi" }
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
100.times do
|
10
|
+
table id: "test", class: "a b c d e f g" do
|
11
|
+
tr do
|
12
|
+
td id: "test", class: "a b c d e f g" do
|
13
|
+
span { "Hi" }
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
td id: "test", class: "a b c d e f g" do
|
17
|
+
span { "Hi" }
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
td id: "test", class: "a b c d e f g" do
|
21
|
+
span { "Hi" }
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
td id: "test", class: "a b c d e f g" do
|
25
|
+
span { "Hi" }
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
28
|
+
td id: "test", class: "a b c d e f g" do
|
29
|
+
span { "Hi" }
|
30
|
+
end
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
data/gd/phlex/sgml/attributes.rb
CHANGED
@@ -2,9 +2,21 @@
|
|
2
2
|
|
3
3
|
include TestHelper
|
4
4
|
|
5
|
+
class ToPhlexAttributeValueable
|
6
|
+
def to_phlex_attribute_value
|
7
|
+
"to_phlex_attribute_value"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class ToSable
|
12
|
+
def to_s
|
13
|
+
"to_s"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
5
17
|
class ToStrable
|
6
18
|
def to_str
|
7
|
-
"
|
19
|
+
"to_str"
|
8
20
|
end
|
9
21
|
end
|
10
22
|
|
@@ -40,12 +52,28 @@ test "with a set of symbols and strings" do
|
|
40
52
|
expect(component.new).to_render %(<div class="bg-red-500 rounded"></div>)
|
41
53
|
end
|
42
54
|
|
55
|
+
test "with a to_phlex_attribute_value-able object" do
|
56
|
+
component = build_component_with_template do
|
57
|
+
div class: ToPhlexAttributeValueable.new
|
58
|
+
end
|
59
|
+
|
60
|
+
expect(component.new).to_render %(<div class="to_phlex_attribute_value"></div>)
|
61
|
+
end
|
62
|
+
|
63
|
+
test "with a to_s-able object" do
|
64
|
+
component = build_component_with_template do
|
65
|
+
div class: ToSable.new
|
66
|
+
end
|
67
|
+
|
68
|
+
expect(component.new).to_render %(<div class="to_s"></div>)
|
69
|
+
end
|
70
|
+
|
43
71
|
test "with a to_str-able object" do
|
44
72
|
component = build_component_with_template do
|
45
73
|
div class: ToStrable.new
|
46
74
|
end
|
47
75
|
|
48
|
-
expect(component.new).to_render %(<div class="
|
76
|
+
expect(component.new).to_render %(<div class="to_str"></div>)
|
49
77
|
end
|
50
78
|
|
51
79
|
test "with numeric integer/float" do
|
data/lib/phlex/black_hole.rb
CHANGED
data/lib/phlex/context.rb
CHANGED
@@ -2,26 +2,63 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
class Phlex::Context
|
5
|
-
def initialize
|
6
|
-
@
|
5
|
+
def initialize(user_context = {})
|
6
|
+
@buffer = +""
|
7
7
|
@capturing = false
|
8
|
+
@user_context = user_context
|
9
|
+
@fragments = nil
|
10
|
+
@in_target_fragment = false
|
11
|
+
@halt_signal = nil
|
8
12
|
end
|
9
13
|
|
10
|
-
attr_accessor :
|
14
|
+
attr_accessor :buffer, :capturing, :user_context, :in_target_fragment
|
11
15
|
|
12
|
-
|
13
|
-
|
16
|
+
attr_reader :fragments
|
17
|
+
|
18
|
+
# Added for backwards compatibility with phlex-rails. We can remove this with 2.0
|
19
|
+
def target
|
20
|
+
@buffer
|
21
|
+
end
|
22
|
+
|
23
|
+
def target_fragments(fragments)
|
24
|
+
@fragments = fragments.to_h { |it| [it, true] }
|
25
|
+
end
|
26
|
+
|
27
|
+
def around_render
|
28
|
+
return yield if !@fragments || @halt_signal
|
29
|
+
|
30
|
+
catch do |signal|
|
31
|
+
@halt_signal = signal
|
32
|
+
yield
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def begin_target(id)
|
37
|
+
@in_target_fragment = id
|
38
|
+
end
|
39
|
+
|
40
|
+
def end_target
|
41
|
+
@fragments.delete(@in_target_fragment)
|
42
|
+
@in_target_fragment = false
|
43
|
+
throw @halt_signal if @fragments.length == 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def capturing_into(new_buffer)
|
47
|
+
original_buffer = @buffer
|
14
48
|
original_capturing = @capturing
|
49
|
+
original_fragments = @fragments
|
15
50
|
|
16
51
|
begin
|
17
|
-
@
|
52
|
+
@buffer = new_buffer
|
18
53
|
@capturing = true
|
54
|
+
@fragments = nil
|
19
55
|
yield
|
20
56
|
ensure
|
21
|
-
@
|
57
|
+
@buffer = original_buffer
|
22
58
|
@capturing = original_capturing
|
59
|
+
@fragments = original_fragments
|
23
60
|
end
|
24
61
|
|
25
|
-
|
62
|
+
new_buffer
|
26
63
|
end
|
27
64
|
end
|
data/lib/phlex/csv.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Phlex::CSV
|
4
|
+
include Phlex::Callable
|
5
|
+
|
6
|
+
FORMULA_PREFIXES = ["=", "+", "-", "@", "\t", "\r"].to_h { |prefix| [prefix, true] }.freeze
|
7
|
+
SPACE_CHARACTERS = [" ", "\t", "\r"].to_h { |char| [char, true] }.freeze
|
8
|
+
|
9
|
+
def initialize(collection)
|
10
|
+
@collection = collection
|
11
|
+
@_headers = []
|
12
|
+
@_current_row = []
|
13
|
+
@_current_column_index = 0
|
14
|
+
@_view_context = nil
|
15
|
+
@_first = true
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :collection
|
19
|
+
|
20
|
+
def call(buffer = +"", view_context: nil)
|
21
|
+
unless escape_csv_injection? == true || escape_csv_injection? == false
|
22
|
+
raise <<~MESSAGE
|
23
|
+
You need to define escape_csv_injection? in #{self.class.name}, returning either `true` or `false`.
|
24
|
+
|
25
|
+
CSV injection is a security vulnerability where malicious spreadsheet formulae are used to execute code or exfiltrate data when a CSV is opened in a spreadsheet program such as Microsoft Excel or Google Sheets.
|
26
|
+
|
27
|
+
For more information, see https://owasp.org/www-community/attacks/CSV_Injection
|
28
|
+
|
29
|
+
If you're sure this CSV will never be opened in a spreadsheet program, you can disable CSV injection escapes:
|
30
|
+
|
31
|
+
def escape_csv_injection? = false
|
32
|
+
|
33
|
+
This is useful when using CSVs for byte-for-byte data exchange between secure systems.
|
34
|
+
|
35
|
+
Alternatively, you can enable CSV injection escapes at the cost of data integrity:
|
36
|
+
|
37
|
+
def escape_csv_injection? = true
|
38
|
+
|
39
|
+
Note: Enabling the CSV injection escapes will prefix any values that start with `=`, `+`, `-`, `@`, `\\t`, or `\\r` with a single quote `'` to prevent them from being interpreted as formulae by spreadsheet programs.
|
40
|
+
|
41
|
+
Unfortunately, there is no one-size-fits-all solution to CSV injection. You need to decide based on your specific use case.
|
42
|
+
MESSAGE
|
43
|
+
end
|
44
|
+
|
45
|
+
@_view_context = view_context
|
46
|
+
|
47
|
+
each_item do |record|
|
48
|
+
yielder(record) do |*args, **kwargs|
|
49
|
+
view_template(*args, **kwargs)
|
50
|
+
|
51
|
+
if @_first && render_headers?
|
52
|
+
buffer << @_headers.join(",") << "\n"
|
53
|
+
end
|
54
|
+
|
55
|
+
buffer << @_current_row.join(",") << "\n"
|
56
|
+
@_current_column_index = 0
|
57
|
+
@_current_row.clear
|
58
|
+
end
|
59
|
+
|
60
|
+
@_first = false
|
61
|
+
end
|
62
|
+
|
63
|
+
buffer
|
64
|
+
end
|
65
|
+
|
66
|
+
def filename
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def content_type
|
71
|
+
"text/csv"
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def column(header = nil, value)
|
77
|
+
if @_first
|
78
|
+
@_headers << escape(header)
|
79
|
+
elsif header != @_headers[@_current_column_index]
|
80
|
+
raise "Inconsistent header."
|
81
|
+
end
|
82
|
+
|
83
|
+
@_current_row << escape(value)
|
84
|
+
@_current_column_index += 1
|
85
|
+
end
|
86
|
+
|
87
|
+
def each_item(&block)
|
88
|
+
collection.each(&block)
|
89
|
+
end
|
90
|
+
|
91
|
+
def yielder(record)
|
92
|
+
yield(record)
|
93
|
+
end
|
94
|
+
|
95
|
+
def template(...)
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
|
99
|
+
# Override and set to `false` to disable rendering headers.
|
100
|
+
def render_headers?
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
104
|
+
# Override and set to `true` to strip leading and trailing whitespace from values.
|
105
|
+
def trim_whitespace?
|
106
|
+
false
|
107
|
+
end
|
108
|
+
|
109
|
+
# Override and set to `false` to disable CSV injection escapes or `true` to enable.
|
110
|
+
def escape_csv_injection?
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
def helpers
|
115
|
+
@_view_context
|
116
|
+
end
|
117
|
+
|
118
|
+
def escape(value)
|
119
|
+
value = trim_whitespace? ? value.to_s.strip : value.to_s
|
120
|
+
first_char = value[0]
|
121
|
+
last_char = value[-1]
|
122
|
+
|
123
|
+
if escape_csv_injection? && FORMULA_PREFIXES[first_char]
|
124
|
+
# Prefix a single quote to prevent Excel, Google Docs, etc. from interpreting the value as a formula.
|
125
|
+
# See https://owasp.org/www-community/attacks/CSV_Injection
|
126
|
+
%("'#{value.gsub('"', '""')}")
|
127
|
+
elsif (!trim_whitespace? && (SPACE_CHARACTERS[first_char] || SPACE_CHARACTERS[last_char])) || value.include?('"') || value.include?(",") || value.include?("\n")
|
128
|
+
%("#{value.gsub('"', '""')}")
|
129
|
+
else
|
130
|
+
value
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/phlex/elements.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0")
|
4
|
-
using Phlex::Overrides::Symbol::Name
|
5
|
-
end
|
6
|
-
|
7
3
|
# Extending this module provides the {register_element} macro for registering your own custom elements. It's already extended by {HTML} and {SVG}.
|
8
4
|
# @example
|
9
5
|
# module MyCustomElements
|
@@ -15,14 +11,14 @@ end
|
|
15
11
|
# class MyComponent < Phlex::HTML
|
16
12
|
# include MyCustomElements
|
17
13
|
#
|
18
|
-
# def
|
14
|
+
# def view_template
|
19
15
|
# trix_editor
|
20
16
|
# end
|
21
17
|
# end
|
22
18
|
module Phlex::Elements
|
23
19
|
# @api private
|
24
20
|
def registered_elements
|
25
|
-
@registered_elements ||=
|
21
|
+
@registered_elements ||= {}
|
26
22
|
end
|
27
23
|
|
28
24
|
# Register a custom element. This macro defines an element method for the current class and descendents only. There is no global element registry.
|
@@ -32,35 +28,64 @@ module Phlex::Elements
|
|
32
28
|
# @note The methods defined by this macro depend on other methods from {SGML} so they should always be mixed into an {HTML} or {SVG} component.
|
33
29
|
# @example Register the custom element `<trix-editor>`
|
34
30
|
# register_element :trix_editor
|
35
|
-
def register_element(method_name, tag:
|
36
|
-
|
31
|
+
def register_element(method_name, tag: method_name.name.tr("_", "-"), deprecated: false)
|
32
|
+
if deprecated
|
33
|
+
deprecation = <<~RUBY
|
34
|
+
Kernel.warn "#{deprecated}"
|
35
|
+
RUBY
|
36
|
+
else
|
37
|
+
deprecation = ""
|
38
|
+
end
|
37
39
|
|
38
40
|
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
39
41
|
# frozen_string_literal: true
|
40
42
|
|
41
43
|
def #{method_name}(**attributes, &block)
|
42
|
-
|
44
|
+
#{deprecation}
|
45
|
+
|
46
|
+
context = @_context
|
47
|
+
buffer = context.buffer
|
48
|
+
fragment = context.fragments
|
49
|
+
target_found = false
|
50
|
+
|
51
|
+
if fragment
|
52
|
+
return if fragment.length == 0 # we found all our fragments already
|
53
|
+
|
54
|
+
id = attributes[:id]
|
55
|
+
|
56
|
+
if !context.in_target_fragment
|
57
|
+
if fragment[id]
|
58
|
+
context.begin_target(id)
|
59
|
+
target_found = true
|
60
|
+
else
|
61
|
+
yield(self) if block
|
62
|
+
return nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
43
66
|
|
44
67
|
if attributes.length > 0 # with attributes
|
45
68
|
if block # with content block
|
46
|
-
|
69
|
+
buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[respond_to?(:process_attributes) ? (attributes.hash + self.class.hash) : attributes.hash] || __attributes__(**attributes)) << ">"
|
47
70
|
yield_content(&block)
|
48
|
-
|
71
|
+
buffer << "</#{tag}>"
|
49
72
|
else # without content block
|
50
|
-
|
73
|
+
buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[respond_to?(:process_attributes) ? (attributes.hash + self.class.hash) : attributes.hash] || __attributes__(**attributes)) << "></#{tag}>"
|
51
74
|
end
|
52
75
|
else # without attributes
|
53
76
|
if block # with content block
|
54
|
-
|
77
|
+
buffer << "<#{tag}>"
|
55
78
|
yield_content(&block)
|
56
|
-
|
79
|
+
buffer << "</#{tag}>"
|
57
80
|
else # without content block
|
58
|
-
|
81
|
+
buffer << "<#{tag}></#{tag}>"
|
59
82
|
end
|
60
83
|
end
|
61
84
|
|
62
85
|
#{'flush' if tag == 'head'}
|
63
86
|
|
87
|
+
context.end_target if target_found
|
88
|
+
|
64
89
|
nil
|
65
90
|
end
|
66
91
|
|
@@ -73,19 +98,47 @@ module Phlex::Elements
|
|
73
98
|
end
|
74
99
|
|
75
100
|
# @api private
|
76
|
-
def register_void_element(method_name, tag: method_name.name.tr("_", "-"))
|
101
|
+
def register_void_element(method_name, tag: method_name.name.tr("_", "-"), deprecated: false)
|
102
|
+
if deprecated
|
103
|
+
deprecation = <<~RUBY
|
104
|
+
Kernel.warn "#{deprecated}"
|
105
|
+
RUBY
|
106
|
+
else
|
107
|
+
deprecation = ""
|
108
|
+
end
|
109
|
+
|
77
110
|
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
78
111
|
# frozen_string_literal: true
|
79
112
|
|
80
113
|
def #{method_name}(**attributes)
|
81
|
-
|
114
|
+
#{deprecation}
|
115
|
+
context = @_context
|
116
|
+
buffer = context.buffer
|
117
|
+
fragment = context.fragments
|
118
|
+
|
119
|
+
if fragment
|
120
|
+
return if fragment.length == 0 # we found all our fragments already
|
121
|
+
|
122
|
+
id = attributes[:id]
|
123
|
+
|
124
|
+
if !context.in_target_fragment
|
125
|
+
if fragment[id]
|
126
|
+
context.begin_target(id)
|
127
|
+
target_found = true
|
128
|
+
else
|
129
|
+
return nil
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
82
133
|
|
83
134
|
if attributes.length > 0 # with attributes
|
84
|
-
|
135
|
+
buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[respond_to?(:process_attributes) ? (attributes.hash + self.class.hash) : attributes.hash] || __attributes__(**attributes)) << ">"
|
85
136
|
else # without attributes
|
86
|
-
|
137
|
+
buffer << "<#{tag}>"
|
87
138
|
end
|
88
139
|
|
140
|
+
context.end_target if target_found
|
141
|
+
|
89
142
|
nil
|
90
143
|
end
|
91
144
|
|