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.

Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +8 -0
  3. data/.rubocop.yml +21 -5
  4. data/Gemfile +26 -12
  5. data/Procfile.dev +3 -0
  6. data/README.md +1 -1
  7. data/Rakefile +3 -5
  8. data/SECURITY.md +1 -1
  9. data/bench.rb +7 -0
  10. data/config/sus.rb +15 -0
  11. data/docs/assets/application.css +6 -0
  12. data/docs/build.rb +17 -10
  13. data/docs/components/callout.rb +1 -1
  14. data/docs/components/code_block.rb +2 -2
  15. data/docs/components/code_span.rb +9 -0
  16. data/docs/components/example.rb +5 -5
  17. data/docs/components/heading.rb +2 -2
  18. data/docs/components/layout.rb +62 -17
  19. data/docs/components/markdown.rb +14 -15
  20. data/docs/components/nav/item.rb +33 -0
  21. data/docs/components/nav.rb +6 -0
  22. data/docs/components/tabs/tab.rb +4 -2
  23. data/docs/components/tabs.rb +1 -1
  24. data/docs/components/title.rb +2 -2
  25. data/docs/page_builder.rb +3 -0
  26. data/docs/pages/application_page.rb +1 -1
  27. data/docs/pages/helpers.rb +97 -0
  28. data/docs/pages/index.rb +6 -17
  29. data/docs/pages/library/collections.rb +83 -0
  30. data/docs/pages/rails/getting_started.rb +53 -0
  31. data/docs/pages/rails/helpers.rb +55 -0
  32. data/docs/pages/rails/layouts.rb +61 -0
  33. data/docs/pages/rails/migrating.rb +37 -0
  34. data/docs/pages/rails/rendering_views.rb +35 -0
  35. data/docs/pages/templates.rb +53 -151
  36. data/docs/pages/testing/capybara.rb +48 -0
  37. data/docs/pages/testing/getting_started.rb +44 -0
  38. data/docs/pages/testing/nokogiri.rb +83 -0
  39. data/docs/pages/testing/rails.rb +17 -0
  40. data/docs/pages/translations.rb +81 -0
  41. data/docs/pages/views.rb +87 -78
  42. data/fixtures/compiler_test_helpers.rb +19 -0
  43. data/fixtures/content.rb +60 -0
  44. data/fixtures/dummy/app/components/comment_component.html.erb +14 -0
  45. data/fixtures/dummy/app/components/comment_component.rb +8 -0
  46. data/fixtures/dummy/app/components/reaction_component.html.erb +3 -0
  47. data/fixtures/dummy/app/components/reaction_component.rb +7 -0
  48. data/fixtures/dummy/app/controllers/comments_controller.rb +4 -0
  49. data/fixtures/dummy/app/views/application_view.rb +8 -0
  50. data/fixtures/dummy/app/views/articles/form.rb +3 -1
  51. data/fixtures/dummy/app/views/card.rb +4 -2
  52. data/fixtures/dummy/app/views/comments/comment.rb +25 -0
  53. data/fixtures/dummy/app/views/comments/index.html.erb +3 -0
  54. data/fixtures/dummy/app/views/comments/reaction.rb +17 -0
  55. data/fixtures/dummy/app/views/comments/show.html.erb +3 -0
  56. data/fixtures/dummy/app/views/heading.rb +1 -1
  57. data/fixtures/layout.rb +5 -5
  58. data/fixtures/page.rb +18 -24
  59. data/fixtures/{test_helper.rb → rails_helper.rb} +3 -7
  60. data/fixtures/standard_element.rb +87 -0
  61. data/fixtures/view_helper.rb +1 -1
  62. data/fixtures/void_element.rb +31 -0
  63. data/lib/generators/phlex/collection/USAGE +8 -0
  64. data/lib/generators/phlex/collection/collection_generator.rb +13 -0
  65. data/lib/generators/phlex/collection/templates/collection.rb.erb +16 -0
  66. data/lib/generators/phlex/controller/USAGE +10 -0
  67. data/lib/generators/phlex/controller/controller_generator.rb +54 -0
  68. data/lib/generators/phlex/controller/templates/controller.rb.erb +10 -0
  69. data/lib/generators/phlex/controller/templates/view.rb.erb +14 -0
  70. data/lib/generators/phlex/layout/USAGE +8 -0
  71. data/lib/generators/phlex/layout/layout_generator.rb +13 -0
  72. data/lib/generators/phlex/layout/templates/layout.rb.erb +31 -0
  73. data/lib/generators/phlex/page/USAGE +8 -0
  74. data/lib/generators/phlex/page/page_generator.rb +13 -0
  75. data/lib/generators/phlex/page/templates/page.rb.erb +13 -0
  76. data/lib/generators/phlex/table/USAGE +8 -0
  77. data/lib/generators/phlex/table/table_generator.rb +14 -0
  78. data/lib/generators/phlex/table/templates/table.rb.erb +11 -0
  79. data/lib/generators/phlex/view/templates/view.rb.erb +7 -1
  80. data/lib/generators/phlex/view/view_generator.rb +9 -1
  81. data/lib/install/phlex.rb +10 -1
  82. data/lib/phlex/block.rb +2 -4
  83. data/lib/phlex/buffered.rb +6 -8
  84. data/lib/phlex/callable.rb +9 -0
  85. data/lib/phlex/collection.rb +33 -0
  86. data/lib/phlex/compiler/elements.rb +49 -0
  87. data/lib/phlex/compiler/generators/content.rb +103 -0
  88. data/lib/phlex/compiler/generators/element.rb +61 -0
  89. data/lib/phlex/compiler/nodes/base.rb +19 -0
  90. data/lib/phlex/compiler/nodes/call.rb +9 -0
  91. data/lib/phlex/compiler/nodes/command.rb +13 -0
  92. data/lib/phlex/compiler/nodes/fcall.rb +18 -0
  93. data/lib/phlex/compiler/nodes/method_add_block.rb +33 -0
  94. data/lib/phlex/compiler/nodes/vcall.rb +9 -0
  95. data/lib/phlex/compiler/optimizer.rb +66 -0
  96. data/lib/phlex/compiler/visitors/base.rb +15 -0
  97. data/lib/phlex/compiler/visitors/file.rb +23 -11
  98. data/lib/phlex/compiler/visitors/stable_scope.rb +28 -0
  99. data/lib/phlex/compiler/visitors/statements.rb +36 -0
  100. data/lib/phlex/compiler/visitors/view.rb +19 -0
  101. data/lib/phlex/compiler/visitors/view_method.rb +59 -0
  102. data/lib/phlex/compiler.rb +23 -3
  103. data/lib/phlex/elements.rb +57 -0
  104. data/lib/phlex/engine.rb +0 -3
  105. data/lib/phlex/helpers.rb +59 -0
  106. data/lib/phlex/html/callbacks.rb +11 -0
  107. data/lib/phlex/html.rb +209 -54
  108. data/lib/phlex/markdown.rb +76 -0
  109. data/lib/phlex/rails/form.rb +67 -0
  110. data/lib/phlex/rails/helpers.rb +118 -0
  111. data/lib/phlex/rails/layout.rb +15 -0
  112. data/lib/phlex/rails.rb +10 -0
  113. data/lib/phlex/renderable.rb +9 -3
  114. data/lib/phlex/table.rb +104 -0
  115. data/lib/phlex/testing/capybara.rb +25 -0
  116. data/lib/phlex/testing/nokogiri.rb +24 -0
  117. data/lib/phlex/testing/rails.rb +19 -0
  118. data/lib/phlex/testing/view_helper.rb +15 -0
  119. data/lib/phlex/translation.rb +23 -0
  120. data/lib/phlex/turbo/frame.rb +21 -0
  121. data/lib/phlex/turbo/stream.rb +18 -0
  122. data/lib/phlex/version.rb +1 -1
  123. data/lib/phlex.rb +22 -24
  124. metadata +112 -15
  125. data/.rspec +0 -1
  126. data/fixtures/compilation/vcall.rb +0 -38
  127. data/lib/phlex/compiler/generators/standard_element.rb +0 -30
  128. data/lib/phlex/compiler/generators/void_element.rb +0 -29
  129. data/lib/phlex/compiler/optimizers/base_optimizer.rb +0 -34
  130. data/lib/phlex/compiler/optimizers/vcall.rb +0 -29
  131. data/lib/phlex/compiler/visitors/base_visitor.rb +0 -19
  132. data/lib/phlex/compiler/visitors/component.rb +0 -28
  133. data/lib/phlex/compiler/visitors/component_method.rb +0 -28
  134. data/lib/phlex/rails/tag_helpers.rb +0 -29
  135. data/lib/phlex/view.rb +0 -223
@@ -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::View.instance_method(method_name).bind(prototype)
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 redefine(method)
25
- @view.class_eval(method)
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
@@ -4,8 +4,5 @@ require "rails/engine"
4
4
 
5
5
  module Phlex
6
6
  class Engine < ::Rails::Engine
7
- initializer "phlex.tag_helpers" do
8
- Phlex::View.include(Phlex::Rails::TagHelpers)
9
- end
10
7
  end
11
8
  end
@@ -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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex::HTML::Callbacks
4
+ def template(&block)
5
+ before_rendering_template if respond_to?(:before_rendering_template)
6
+
7
+ super
8
+
9
+ after_rendering_template if respond_to?(:after_rendering_template)
10
+ end
11
+ 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
- module HTML
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
- def register_element(element, tag: element.name.tr("_", "-"))
129
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
130
- # frozen_string_literal: true
131
-
132
- def #{element}(content = nil, **attributes, &block)
133
- if attributes.length > 0
134
- if content
135
- @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << ">" << CGI.escape_html(content) << "</#{tag}>"
136
- elsif block_given?
137
- @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << ">"
138
- yield_content(&block)
139
- @_target << "</#{tag}>"
140
- else
141
- @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << "></#{tag}>"
142
- end
143
- else
144
- if content
145
- case content
146
- when String
147
- @_target << "<#{tag}>" << CGI.escape_html(content) << "</#{tag}>"
148
- when Symbol
149
- @_target << "<#{tag}>" << CGI.escape_html(content.name) << "</#{tag}>"
150
- else
151
- @_target << "<#{tag}>" << CGI.escape_html(content.to_s) << "</#{tag}>"
152
- end
153
- elsif block_given?
154
- @_target << "<#{tag}>"
155
- yield_content(&block)
156
- @_target << "</#{tag}>"
157
- else
158
- @_target << "<#{tag}></#{tag}>"
159
- end
160
- end
161
-
162
- nil
163
- end
164
- RUBY
165
- end
166
-
167
- def register_void_element(element, tag: element.name.tr("_", "-"))
168
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
169
- # frozen_string_literal: true
170
-
171
- def #{element}(**attributes)
172
- if attributes.length > 0
173
- @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << " />"
174
- else
175
- @_target << "<#{tag} />"
176
- end
177
-
178
- nil
179
- end
180
- RUBY
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