phlex 0.3.2 → 0.5.0
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/.editorconfig +8 -0
- data/.rubocop.yml +21 -5
- data/Gemfile +26 -12
- data/Procfile.dev +3 -0
- data/README.md +1 -1
- data/Rakefile +3 -5
- data/SECURITY.md +1 -1
- data/bench.rb +7 -0
- data/config/sus.rb +15 -0
- data/docs/assets/application.css +6 -0
- data/docs/build.rb +17 -10
- data/docs/components/callout.rb +1 -1
- data/docs/components/code_block.rb +2 -2
- data/docs/components/code_span.rb +9 -0
- data/docs/components/example.rb +5 -5
- data/docs/components/heading.rb +2 -2
- data/docs/components/layout.rb +62 -17
- data/docs/components/markdown.rb +14 -15
- data/docs/components/nav/item.rb +33 -0
- data/docs/components/nav.rb +6 -0
- data/docs/components/tabs/tab.rb +4 -2
- data/docs/components/tabs.rb +1 -1
- data/docs/components/title.rb +2 -2
- data/docs/page_builder.rb +3 -0
- data/docs/pages/application_page.rb +1 -1
- data/docs/pages/helpers.rb +97 -0
- data/docs/pages/index.rb +6 -17
- data/docs/pages/library/collections.rb +83 -0
- data/docs/pages/rails/getting_started.rb +53 -0
- data/docs/pages/rails/helpers.rb +55 -0
- data/docs/pages/rails/layouts.rb +61 -0
- data/docs/pages/rails/migrating.rb +37 -0
- data/docs/pages/rails/rendering_views.rb +35 -0
- data/docs/pages/templates.rb +53 -151
- data/docs/pages/testing/capybara.rb +48 -0
- data/docs/pages/testing/getting_started.rb +44 -0
- data/docs/pages/testing/nokogiri.rb +83 -0
- data/docs/pages/testing/rails.rb +17 -0
- data/docs/pages/translations.rb +81 -0
- data/docs/pages/views.rb +87 -78
- data/fixtures/compiler_test_helpers.rb +19 -0
- data/fixtures/content.rb +60 -0
- data/fixtures/dummy/app/components/comment_component.html.erb +14 -0
- data/fixtures/dummy/app/components/comment_component.rb +8 -0
- data/fixtures/dummy/app/components/reaction_component.html.erb +3 -0
- data/fixtures/dummy/app/components/reaction_component.rb +7 -0
- data/fixtures/dummy/app/controllers/comments_controller.rb +4 -0
- data/fixtures/dummy/app/views/application_view.rb +8 -0
- data/fixtures/dummy/app/views/articles/form.rb +3 -1
- data/fixtures/dummy/app/views/card.rb +4 -2
- data/fixtures/dummy/app/views/comments/comment.rb +25 -0
- data/fixtures/dummy/app/views/comments/index.html.erb +3 -0
- data/fixtures/dummy/app/views/comments/reaction.rb +17 -0
- data/fixtures/dummy/app/views/comments/show.html.erb +3 -0
- data/fixtures/dummy/app/views/heading.rb +1 -1
- data/fixtures/layout.rb +5 -5
- data/fixtures/page.rb +18 -24
- data/fixtures/{test_helper.rb → rails_helper.rb} +3 -7
- data/fixtures/standard_element.rb +87 -0
- data/fixtures/view_helper.rb +1 -1
- data/fixtures/void_element.rb +31 -0
- data/lib/generators/phlex/collection/USAGE +8 -0
- data/lib/generators/phlex/collection/collection_generator.rb +13 -0
- data/lib/generators/phlex/collection/templates/collection.rb.erb +16 -0
- data/lib/generators/phlex/controller/USAGE +10 -0
- data/lib/generators/phlex/controller/controller_generator.rb +54 -0
- data/lib/generators/phlex/controller/templates/controller.rb.erb +10 -0
- data/lib/generators/phlex/controller/templates/view.rb.erb +14 -0
- data/lib/generators/phlex/layout/USAGE +8 -0
- data/lib/generators/phlex/layout/layout_generator.rb +13 -0
- data/lib/generators/phlex/layout/templates/layout.rb.erb +31 -0
- data/lib/generators/phlex/page/USAGE +8 -0
- data/lib/generators/phlex/page/page_generator.rb +13 -0
- data/lib/generators/phlex/page/templates/page.rb.erb +13 -0
- data/lib/generators/phlex/table/USAGE +8 -0
- data/lib/generators/phlex/table/table_generator.rb +14 -0
- data/lib/generators/phlex/table/templates/table.rb.erb +11 -0
- data/lib/generators/phlex/view/templates/view.rb.erb +7 -1
- data/lib/generators/phlex/view/view_generator.rb +9 -1
- data/lib/install/phlex.rb +10 -1
- data/lib/phlex/block.rb +2 -4
- data/lib/phlex/buffered.rb +6 -8
- data/lib/phlex/callable.rb +9 -0
- data/lib/phlex/collection.rb +33 -0
- data/lib/phlex/compiler/elements.rb +49 -0
- data/lib/phlex/compiler/generators/content.rb +103 -0
- data/lib/phlex/compiler/generators/element.rb +61 -0
- data/lib/phlex/compiler/nodes/base.rb +19 -0
- data/lib/phlex/compiler/nodes/call.rb +9 -0
- data/lib/phlex/compiler/nodes/command.rb +13 -0
- data/lib/phlex/compiler/nodes/fcall.rb +18 -0
- data/lib/phlex/compiler/nodes/method_add_block.rb +33 -0
- data/lib/phlex/compiler/nodes/vcall.rb +9 -0
- data/lib/phlex/compiler/optimizer.rb +66 -0
- data/lib/phlex/compiler/visitors/base.rb +15 -0
- data/lib/phlex/compiler/visitors/file.rb +23 -11
- data/lib/phlex/compiler/visitors/stable_scope.rb +28 -0
- data/lib/phlex/compiler/visitors/statements.rb +36 -0
- data/lib/phlex/compiler/visitors/view.rb +19 -0
- data/lib/phlex/compiler/visitors/view_method.rb +59 -0
- data/lib/phlex/compiler.rb +23 -3
- data/lib/phlex/elements.rb +57 -0
- data/lib/phlex/engine.rb +0 -3
- data/lib/phlex/helpers.rb +59 -0
- data/lib/phlex/html/callbacks.rb +11 -0
- data/lib/phlex/html.rb +209 -54
- data/lib/phlex/markdown.rb +76 -0
- data/lib/phlex/rails/form.rb +67 -0
- data/lib/phlex/rails/helpers.rb +118 -0
- data/lib/phlex/rails/layout.rb +15 -0
- data/lib/phlex/rails.rb +10 -0
- data/lib/phlex/renderable.rb +9 -3
- data/lib/phlex/table.rb +104 -0
- data/lib/phlex/testing/capybara.rb +25 -0
- data/lib/phlex/testing/nokogiri.rb +24 -0
- data/lib/phlex/testing/rails.rb +19 -0
- data/lib/phlex/testing/view_helper.rb +15 -0
- data/lib/phlex/translation.rb +23 -0
- data/lib/phlex/turbo/frame.rb +21 -0
- data/lib/phlex/turbo/stream.rb +18 -0
- data/lib/phlex/version.rb +1 -1
- data/lib/phlex.rb +22 -24
- metadata +112 -15
- data/.rspec +0 -1
- data/fixtures/compilation/vcall.rb +0 -38
- data/lib/phlex/compiler/generators/standard_element.rb +0 -30
- data/lib/phlex/compiler/generators/void_element.rb +0 -29
- data/lib/phlex/compiler/optimizers/base_optimizer.rb +0 -34
- data/lib/phlex/compiler/optimizers/vcall.rb +0 -29
- data/lib/phlex/compiler/visitors/base_visitor.rb +0 -19
- data/lib/phlex/compiler/visitors/component.rb +0 -28
- data/lib/phlex/compiler/visitors/component_method.rb +0 -28
- data/lib/phlex/rails/tag_helpers.rb +0 -29
- data/lib/phlex/view.rb +0 -223
data/lib/phlex/compiler.rb
CHANGED
@@ -6,6 +6,8 @@ module Phlex
|
|
6
6
|
@view = view
|
7
7
|
end
|
8
8
|
|
9
|
+
attr_writer :scope
|
10
|
+
|
9
11
|
def inspect
|
10
12
|
"#{self.class.name} for #{@view.name} view class"
|
11
13
|
end
|
@@ -14,15 +16,33 @@ module Phlex
|
|
14
16
|
Visitors::File.new(self).visit(tree)
|
15
17
|
end
|
16
18
|
|
19
|
+
def tag_method?(method_name)
|
20
|
+
(HTML::STANDARD_ELEMENTS.key?(method_name) || HTML::VOID_ELEMENTS.key?(method_name)) && !redefined?(method_name)
|
21
|
+
end
|
22
|
+
|
17
23
|
def redefined?(method_name)
|
18
24
|
prototype = @view.allocate
|
19
25
|
|
20
26
|
@view.instance_method(method_name).bind(prototype) !=
|
21
|
-
Phlex::
|
27
|
+
Phlex::HTML.instance_method(method_name).bind(prototype)
|
28
|
+
end
|
29
|
+
|
30
|
+
def redefine(method, line:)
|
31
|
+
patch = scope + method + unscope
|
32
|
+
eval(patch, Kernel.binding, file, (line - 1))
|
33
|
+
end
|
34
|
+
|
35
|
+
def scope
|
36
|
+
@scope.map do |scope|
|
37
|
+
case scope
|
38
|
+
in SyntaxTree::ModuleDeclaration then "module #{scope.constant.constant.value};"
|
39
|
+
in SyntaxTree::ClassDeclaration then "class #{scope.constant.constant.value};"
|
40
|
+
end
|
41
|
+
end.join + "\n"
|
22
42
|
end
|
23
43
|
|
24
|
-
def
|
25
|
-
@
|
44
|
+
def unscope
|
45
|
+
"; end" * @scope.size
|
26
46
|
end
|
27
47
|
|
28
48
|
def line
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0")
|
4
|
+
using Overrides::Symbol::Name
|
5
|
+
end
|
6
|
+
|
7
|
+
module Phlex
|
8
|
+
module Elements
|
9
|
+
def register_element(element, tag: element.name.tr("_", "-"))
|
10
|
+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
11
|
+
# frozen_string_literal: true
|
12
|
+
|
13
|
+
def #{element}(content = nil, **attributes, &block)
|
14
|
+
if content
|
15
|
+
raise ArgumentError, %(👋 You can no longer pass content to #{element} as a positional argument.\n Instead, you can pass it as a block, e.g. #{element} { "Hello" })
|
16
|
+
end
|
17
|
+
|
18
|
+
if attributes.length > 0
|
19
|
+
if block_given?
|
20
|
+
@_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(**attributes)) << ">"
|
21
|
+
yield_content(&block)
|
22
|
+
@_target << "</#{tag}>"
|
23
|
+
else
|
24
|
+
@_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(**attributes)) << "></#{tag}>"
|
25
|
+
end
|
26
|
+
else
|
27
|
+
if block_given?
|
28
|
+
@_target << "<#{tag}>"
|
29
|
+
yield_content(&block)
|
30
|
+
@_target << "</#{tag}>"
|
31
|
+
else
|
32
|
+
@_target << "<#{tag}></#{tag}>"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
RUBY
|
39
|
+
end
|
40
|
+
|
41
|
+
def register_void_element(element, tag: element.name.tr("_", "-"))
|
42
|
+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
43
|
+
# frozen_string_literal: true
|
44
|
+
|
45
|
+
def #{element}(**attributes)
|
46
|
+
if attributes.length > 0
|
47
|
+
@_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(**attributes)) << ">"
|
48
|
+
else
|
49
|
+
@_target << "<#{tag}>"
|
50
|
+
end
|
51
|
+
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
RUBY
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/phlex/engine.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0")
|
4
|
+
using Overrides::Symbol::Name
|
5
|
+
end
|
6
|
+
|
7
|
+
module Phlex::Helpers
|
8
|
+
def tokens(*tokens, **conditional_tokens)
|
9
|
+
conditional_tokens.each do |condition, token|
|
10
|
+
case condition
|
11
|
+
when Symbol then next unless send(condition)
|
12
|
+
when Proc then next unless condition.call
|
13
|
+
else raise ArgumentError,
|
14
|
+
"The class condition must be a Symbol or a Proc."
|
15
|
+
end
|
16
|
+
|
17
|
+
case token
|
18
|
+
when Symbol then tokens << token.name
|
19
|
+
when String then tokens << token
|
20
|
+
when Array then tokens.concat(token)
|
21
|
+
else raise ArgumentError,
|
22
|
+
"Conditional classes must be Symbols, Strings, or Arrays of Symbols or Strings."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
tokens.compact.join(" ")
|
27
|
+
end
|
28
|
+
|
29
|
+
def classes(*tokens, **conditional_tokens)
|
30
|
+
tokens = self.tokens(*tokens, **conditional_tokens)
|
31
|
+
|
32
|
+
if tokens.empty?
|
33
|
+
{}
|
34
|
+
else
|
35
|
+
{ class: tokens }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def mix(*args)
|
40
|
+
args.each_with_object({}) do |object, result|
|
41
|
+
result.merge!(object) do |_key, old, new|
|
42
|
+
case new
|
43
|
+
when Hash
|
44
|
+
old.is_a?(Hash) ? mix(old, new) : new
|
45
|
+
when Array
|
46
|
+
old.is_a?(Array) ? (old + new) : new
|
47
|
+
when String
|
48
|
+
old.is_a?(String) ? "#{old} #{new}" : new
|
49
|
+
else
|
50
|
+
new
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
result.transform_keys! do |key|
|
55
|
+
key.end_with?("!") ? key.name.chop.to_sym : key
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/phlex/html.rb
CHANGED
@@ -5,7 +5,7 @@ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0")
|
|
5
5
|
end
|
6
6
|
|
7
7
|
module Phlex
|
8
|
-
|
8
|
+
class HTML
|
9
9
|
DOCTYPE = "<!DOCTYPE html>"
|
10
10
|
|
11
11
|
STANDARD_ELEMENTS = {
|
@@ -113,6 +113,7 @@ module Phlex
|
|
113
113
|
area: "area",
|
114
114
|
br: "br",
|
115
115
|
embed: "embed",
|
116
|
+
hr: "hr",
|
116
117
|
img: "img",
|
117
118
|
input: "input",
|
118
119
|
link: "link",
|
@@ -125,59 +126,213 @@ module Phlex
|
|
125
126
|
|
126
127
|
EVENT_ATTRIBUTES = %w[onabort onafterprint onbeforeprint onbeforeunload onblur oncanplay oncanplaythrough onchange onclick oncontextmenu oncopy oncuechange oncut ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onerror onfocus onhashchange oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmessage onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onoffline ononline onpagehide onpageshow onpaste onpause onplay onplaying onpopstate onprogress onratechange onreset onresize onscroll onsearch onseeked onseeking onselect onstalled onstorage onsubmit onsuspend ontimeupdate ontoggle onunload onvolumechange onwaiting onwheel].to_h { [_1, true] }.freeze
|
127
128
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
129
|
+
extend Elements
|
130
|
+
include Helpers
|
131
|
+
include Callable
|
132
|
+
include Renderable
|
133
|
+
|
134
|
+
class << self
|
135
|
+
attr_accessor :rendered_at_least_once
|
136
|
+
|
137
|
+
# def compile
|
138
|
+
# return if @compiled
|
139
|
+
# return unless name
|
140
|
+
# return if name.start_with? "#"
|
141
|
+
#
|
142
|
+
# Compiler.new(self).call
|
143
|
+
#
|
144
|
+
# @compiled = true
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# def compiled?
|
148
|
+
# !!@compiled
|
149
|
+
# end
|
150
|
+
end
|
151
|
+
|
152
|
+
def call(buffer = +"", view_context: nil, parent: nil, &block)
|
153
|
+
return buffer unless render?
|
154
|
+
|
155
|
+
raise "The same view instance shouldn't be rendered twice" if rendered?
|
156
|
+
|
157
|
+
@_rendered = true
|
158
|
+
@_target = buffer
|
159
|
+
@_view_context = view_context
|
160
|
+
@_parent = parent
|
161
|
+
@output_buffer = self
|
162
|
+
|
163
|
+
template(&block)
|
164
|
+
|
165
|
+
self.class.rendered_at_least_once ||= true
|
166
|
+
|
167
|
+
buffer
|
168
|
+
end
|
169
|
+
|
170
|
+
def rendered?
|
171
|
+
@_rendered ||= false
|
172
|
+
end
|
173
|
+
|
174
|
+
def render?
|
175
|
+
true
|
176
|
+
end
|
177
|
+
|
178
|
+
STANDARD_ELEMENTS.each do |method_name, tag|
|
179
|
+
register_element(method_name, tag: tag)
|
180
|
+
end
|
181
|
+
|
182
|
+
VOID_ELEMENTS.each do |method_name, tag|
|
183
|
+
register_void_element(method_name, tag: tag)
|
184
|
+
end
|
185
|
+
|
186
|
+
def yield_content(&block)
|
187
|
+
return unless block_given?
|
188
|
+
|
189
|
+
original_length = @_target.length
|
190
|
+
output = yield(self)
|
191
|
+
unchanged = (original_length == @_target.length)
|
192
|
+
|
193
|
+
if unchanged
|
194
|
+
case output
|
195
|
+
when String, Symbol, Integer, Float
|
196
|
+
text(output)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
nil
|
201
|
+
end
|
202
|
+
|
203
|
+
def text(content)
|
204
|
+
@_target << _output(content)
|
205
|
+
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
|
209
|
+
def _output(content)
|
210
|
+
case content
|
211
|
+
when String then Hescape.escape_html(content)
|
212
|
+
when Symbol then Hescape.escape_html(content.name)
|
213
|
+
else Hescape.escape_html(content.to_s)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def whitespace
|
218
|
+
@_target << " "
|
219
|
+
|
220
|
+
if block_given?
|
221
|
+
yield
|
222
|
+
@_target << " "
|
223
|
+
end
|
224
|
+
|
225
|
+
nil
|
226
|
+
end
|
227
|
+
|
228
|
+
def comment(content = "")
|
229
|
+
@_target << "<!-- " << Hescape.escape_html(content.to_s) << " -->"
|
230
|
+
nil
|
231
|
+
end
|
232
|
+
|
233
|
+
def doctype
|
234
|
+
@_target << DOCTYPE
|
235
|
+
nil
|
236
|
+
end
|
237
|
+
|
238
|
+
def unsafe_raw(content = nil, &block)
|
239
|
+
@_target << (content || instance_exec(&block))
|
240
|
+
nil
|
241
|
+
end
|
242
|
+
|
243
|
+
def html_safe?
|
244
|
+
true
|
245
|
+
end
|
246
|
+
|
247
|
+
def safe_append=(value)
|
248
|
+
return unless value
|
249
|
+
|
250
|
+
@_target << case value
|
251
|
+
when String then value
|
252
|
+
when Symbol then value.name
|
253
|
+
else value.to_s
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def append=(value)
|
258
|
+
return unless value
|
259
|
+
|
260
|
+
if value.html_safe?
|
261
|
+
self.safe_append = value
|
262
|
+
else
|
263
|
+
@_target << case value
|
264
|
+
when String then Hescape.escape_html(value)
|
265
|
+
when Symbol then Hescape.escape_html(value.name)
|
266
|
+
else Hescape.escape_html(value.to_s)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def capture(&block)
|
272
|
+
return unless block_given?
|
273
|
+
|
274
|
+
original_buffer = @_target
|
275
|
+
new_buffer = +""
|
276
|
+
@_target = new_buffer
|
277
|
+
|
278
|
+
yield
|
279
|
+
|
280
|
+
@_target = original_buffer
|
281
|
+
|
282
|
+
new_buffer.html_safe
|
283
|
+
end
|
284
|
+
|
285
|
+
def helpers
|
286
|
+
@_view_context
|
287
|
+
end
|
288
|
+
|
289
|
+
def _attributes(**attributes)
|
290
|
+
if attributes[:href]&.start_with?(/\s*javascript/)
|
291
|
+
attributes[:href] = attributes[:href].sub(/^\s*(javascript:)+/, "")
|
292
|
+
end
|
293
|
+
|
294
|
+
buffer = +""
|
295
|
+
_build_attributes(attributes, buffer: buffer)
|
296
|
+
|
297
|
+
unless self.class.rendered_at_least_once
|
298
|
+
Phlex::ATTRIBUTE_CACHE[attributes.hash] = buffer.freeze
|
299
|
+
end
|
300
|
+
|
301
|
+
buffer
|
302
|
+
end
|
303
|
+
|
304
|
+
def _build_attributes(attributes, buffer:)
|
305
|
+
attributes.each do |k, v|
|
306
|
+
next unless v
|
307
|
+
|
308
|
+
name = case k
|
309
|
+
when String
|
310
|
+
k
|
311
|
+
when Symbol
|
312
|
+
k.name.tr("_", "-")
|
313
|
+
else
|
314
|
+
k.to_s
|
315
|
+
end
|
316
|
+
|
317
|
+
if HTML::EVENT_ATTRIBUTES[name] || name.match?(/[<>&"']/)
|
318
|
+
raise ArgumentError, "Unsafe attribute name detected: #{k}."
|
319
|
+
end
|
320
|
+
|
321
|
+
case v
|
322
|
+
when true
|
323
|
+
buffer << " " << name
|
324
|
+
when String
|
325
|
+
buffer << " " << name << '="' << Hescape.escape_html(v) << '"'
|
326
|
+
when Symbol
|
327
|
+
buffer << " " << name << '="' << Hescape.escape_html(v.name) << '"'
|
328
|
+
when Hash
|
329
|
+
_build_attributes(v.transform_keys { "#{k}-#{_1.name.tr('_', '-')}" }, buffer: buffer)
|
330
|
+
else
|
331
|
+
buffer << " " << name << '="' << Hescape.escape_html(v.to_s) << '"'
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
buffer
|
181
336
|
end
|
182
337
|
end
|
183
338
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "commonmarker"
|
4
|
+
|
5
|
+
module Phlex
|
6
|
+
class Markdown < Phlex::HTML
|
7
|
+
def initialize(content)
|
8
|
+
@content = content
|
9
|
+
end
|
10
|
+
|
11
|
+
def template
|
12
|
+
visit(doc)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def doc
|
18
|
+
CommonMarker.render_doc(@content)
|
19
|
+
end
|
20
|
+
|
21
|
+
def visit(node)
|
22
|
+
return if node.nil?
|
23
|
+
|
24
|
+
case node.type
|
25
|
+
in :document | :softbreak
|
26
|
+
visit_children(node)
|
27
|
+
in :text
|
28
|
+
text(node.string_content)
|
29
|
+
in :header
|
30
|
+
case node.header_level
|
31
|
+
in 1 then h1 { visit_children(node) }
|
32
|
+
in 2 then h2 { visit_children(node) }
|
33
|
+
in 3 then h3 { visit_children(node) }
|
34
|
+
in 4 then h4 { visit_children(node) }
|
35
|
+
in 5 then h5 { visit_children(node) }
|
36
|
+
in 6 then h6 { visit_children(node) }
|
37
|
+
end
|
38
|
+
in :paragraph
|
39
|
+
grandparent = node.parent&.parent
|
40
|
+
|
41
|
+
if grandparent&.type == :list && grandparent&.list_tight
|
42
|
+
visit_children(node)
|
43
|
+
else
|
44
|
+
p { visit_children(node) }
|
45
|
+
end
|
46
|
+
in :link
|
47
|
+
a(href: node.url) { visit_children(node) }
|
48
|
+
in :emph
|
49
|
+
em { visit_children(node) }
|
50
|
+
in :strong
|
51
|
+
strong { visit_children(node) }
|
52
|
+
in :list
|
53
|
+
case node.list_type
|
54
|
+
in :ordered_list then ol { visit_children(node) }
|
55
|
+
in :bullet_list then ul { visit_children(node) }
|
56
|
+
end
|
57
|
+
in :list_item
|
58
|
+
li { visit_children(node) }
|
59
|
+
in :code
|
60
|
+
whitespace { code { text(node.string_content) } }
|
61
|
+
in :code_block
|
62
|
+
code_block(node.string_content, language: node.fence_info) do |**attributes|
|
63
|
+
pre(**attributes) { text(node.string_content) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def code_block(code, language:)
|
69
|
+
yield
|
70
|
+
end
|
71
|
+
|
72
|
+
def visit_children(node)
|
73
|
+
node.each { |c| visit(c) }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex
|
4
|
+
module Rails
|
5
|
+
class Form < Phlex::HTML
|
6
|
+
def initialize(model)
|
7
|
+
@model = model
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.input_field(method_name, type:)
|
11
|
+
define_method method_name do |field, value: @model.attributes[field.to_s], **attributes|
|
12
|
+
input(
|
13
|
+
name: field_name(field),
|
14
|
+
type: type,
|
15
|
+
value: value,
|
16
|
+
**attributes
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def template(&block)
|
22
|
+
form action: @url, method: @method do
|
23
|
+
authenticity_token_field
|
24
|
+
yield_content(&block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def authenticity_token_field
|
29
|
+
input(
|
30
|
+
name: "authenticity_token",
|
31
|
+
type: "hidden",
|
32
|
+
value: @_view_context.form_authenticity_token
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def submit(value)
|
37
|
+
input(
|
38
|
+
name: "commit",
|
39
|
+
type: "submit",
|
40
|
+
value: value
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def url
|
45
|
+
@_view_context.url_for(@model)
|
46
|
+
end
|
47
|
+
|
48
|
+
def field_name(*field)
|
49
|
+
@_view_context.field_name(ActiveModel::Naming.param_key(@model.class), *field)
|
50
|
+
end
|
51
|
+
|
52
|
+
input_field :url_field, type: "url"
|
53
|
+
input_field :text_field, type: "text"
|
54
|
+
input_field :date_field, type: "date"
|
55
|
+
input_field :time_field, type: "time"
|
56
|
+
input_field :week_field, type: "week"
|
57
|
+
input_field :month_field, type: "month"
|
58
|
+
input_field :email_field, type: "email"
|
59
|
+
input_field :color_field, type: "color"
|
60
|
+
input_field :hidden_field, type: "hidden"
|
61
|
+
input_field :search_field, type: "search"
|
62
|
+
input_field :password_field, type: "password"
|
63
|
+
input_field :telephone_field, type: "tel"
|
64
|
+
input_field :datetime_local_field, type: "datetime-local"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|