formalist 0.3.0 → 0.4.0
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 +5 -5
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/.travis.yml +21 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +62 -0
- data/Gemfile +1 -2
- data/bin/console +13 -0
- data/formalist.gemspec +33 -0
- data/lib/formalist/definition.rb +65 -0
- data/lib/formalist/element/class_interface.rb +7 -59
- data/lib/formalist/element.rb +37 -19
- data/lib/formalist/elements/attr.rb +8 -20
- data/lib/formalist/elements/compound_field.rb +5 -4
- data/lib/formalist/elements/field.rb +5 -12
- data/lib/formalist/elements/group.rb +6 -5
- data/lib/formalist/elements/many.rb +28 -29
- data/lib/formalist/elements/section.rb +6 -10
- data/lib/formalist/elements/standard/multi_upload_field.rb +8 -0
- data/lib/formalist/elements/standard/rich_text_area.rb +40 -0
- data/lib/formalist/elements/standard/search_multi_selection_field.rb +20 -0
- data/lib/formalist/elements/standard/search_selection_field.rb +20 -0
- data/lib/formalist/elements/standard/tags_field.rb +16 -0
- data/lib/formalist/elements/standard/upload_field.rb +8 -0
- data/lib/formalist/elements/standard.rb +4 -0
- data/lib/formalist/form/validity_check.rb +54 -0
- data/lib/formalist/form.rb +49 -17
- data/lib/formalist/rich_text/embedded_form_compiler.rb +86 -0
- data/lib/formalist/rich_text/embedded_forms_container/mixin.rb +42 -0
- data/lib/formalist/rich_text/embedded_forms_container/registration.rb +30 -0
- data/lib/formalist/rich_text/embedded_forms_container.rb +9 -0
- data/lib/formalist/rich_text/rendering/embedded_form_renderer.rb +25 -0
- data/lib/formalist/rich_text/rendering/html_compiler.rb +100 -0
- data/lib/formalist/rich_text/rendering/html_renderer.rb +186 -0
- data/lib/formalist/rich_text/validity_check.rb +48 -0
- data/lib/formalist/types.rb +8 -7
- data/lib/formalist/version.rb +1 -1
- metadata +54 -31
- data/Gemfile.lock +0 -105
- data/lib/formalist/element/definition.rb +0 -55
- data/lib/formalist/element/permitted_children.rb +0 -46
- data/lib/formalist/form/definition_context.rb +0 -69
- data/lib/formalist/form/result.rb +0 -24
- data/spec/examples.txt +0 -8
- data/spec/integration/dependency_injection_spec.rb +0 -54
- data/spec/integration/form_spec.rb +0 -104
- data/spec/spec_helper.rb +0 -109
- data/spec/support/constants.rb +0 -11
- data/spec/unit/elements/standard/check_box_spec.rb +0 -33
@@ -0,0 +1,40 @@
|
|
1
|
+
require "formalist/element"
|
2
|
+
require "formalist/elements"
|
3
|
+
require "formalist/types"
|
4
|
+
require "formalist/rich_text/embedded_form_compiler"
|
5
|
+
|
6
|
+
module Formalist
|
7
|
+
class Elements
|
8
|
+
class RichTextArea < Field
|
9
|
+
attribute :box_size, Types::String.enum("single", "small", "normal", "large", "xlarge"), default: "normal"
|
10
|
+
attribute :inline_formatters, Types::Array
|
11
|
+
attribute :block_formatters, Types::Array
|
12
|
+
attribute :embeddable_forms, Types::Dependency.constrained(respond_to: :to_h)
|
13
|
+
|
14
|
+
# FIXME: it would be tidier to have a reader method for each attribute
|
15
|
+
def attributes
|
16
|
+
super.merge(embeddable_forms: embeddable_forms_config)
|
17
|
+
end
|
18
|
+
|
19
|
+
def input
|
20
|
+
input_compiler.(@input)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Replace the form objects with their AST
|
26
|
+
def embeddable_forms_config
|
27
|
+
@attributes[:embeddable_forms].to_h.map { |key, attrs|
|
28
|
+
[key, attrs.merge(form: attrs[:form].to_ast)]
|
29
|
+
}.to_h
|
30
|
+
end
|
31
|
+
|
32
|
+
# TODO: make compiler configurable somehow?
|
33
|
+
def input_compiler
|
34
|
+
RichText::EmbeddedFormCompiler.new(@attributes[:embeddable_forms])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
register :rich_text_area, RichTextArea
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "formalist/element"
|
2
|
+
require "formalist/elements"
|
3
|
+
require "formalist/types"
|
4
|
+
|
5
|
+
module Formalist
|
6
|
+
class Elements
|
7
|
+
class SearchMultiSelectionField < Field
|
8
|
+
attribute :selector_label, Types::String
|
9
|
+
attribute :render_option_as, Types::String
|
10
|
+
attribute :render_selection_as, Types::String
|
11
|
+
attribute :search_url, Types::String
|
12
|
+
attribute :search_per_page, Types::Int
|
13
|
+
attribute :search_params, Types::Hash
|
14
|
+
attribute :search_threshold, Types::Int
|
15
|
+
attribute :selections, Types::Array
|
16
|
+
end
|
17
|
+
|
18
|
+
register :search_multi_selection_field, SearchMultiSelectionField
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "formalist/element"
|
2
|
+
require "formalist/elements"
|
3
|
+
require "formalist/types"
|
4
|
+
|
5
|
+
module Formalist
|
6
|
+
class Elements
|
7
|
+
class SearchSelectionField < Field
|
8
|
+
attribute :selector_label, Types::String
|
9
|
+
attribute :render_option_as, Types::String
|
10
|
+
attribute :render_selection_as, Types::String
|
11
|
+
attribute :search_url, Types::String
|
12
|
+
attribute :search_per_page, Types::Int
|
13
|
+
attribute :search_params, Types::Hash
|
14
|
+
attribute :search_threshold, Types::Int
|
15
|
+
attribute :selection, Types::Hash
|
16
|
+
end
|
17
|
+
|
18
|
+
register :search_selection_field, SearchSelectionField
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "formalist/element"
|
2
|
+
require "formalist/elements"
|
3
|
+
require "formalist/types"
|
4
|
+
|
5
|
+
module Formalist
|
6
|
+
class Elements
|
7
|
+
class TagsField < Field
|
8
|
+
attribute :search_url, Types::String
|
9
|
+
attribute :search_per_page, Types::Int
|
10
|
+
attribute :search_params, Types::Hash
|
11
|
+
attribute :search_threshold, Types::Int
|
12
|
+
end
|
13
|
+
|
14
|
+
register :tags_field, TagsField
|
15
|
+
end
|
16
|
+
end
|
@@ -6,6 +6,14 @@ module Formalist
|
|
6
6
|
class Elements
|
7
7
|
class UploadField < Field
|
8
8
|
attribute :presign_url, Types::String
|
9
|
+
attribute :presign_options, Types::Hash
|
10
|
+
attribute :render_uploaded_as, Types::String
|
11
|
+
attribute :upload_prompt, Types::String
|
12
|
+
attribute :upload_action_label, Types::String
|
13
|
+
attribute :max_file_size, Types::String
|
14
|
+
attribute :max_file_size_message, Types::String
|
15
|
+
attribute :permitted_file_type_message, Types::String
|
16
|
+
attribute :permitted_file_type_regex, Types::String
|
9
17
|
end
|
10
18
|
|
11
19
|
register :upload_field, UploadField
|
@@ -6,8 +6,12 @@ require "formalist/elements/standard/multi_selection_field"
|
|
6
6
|
require "formalist/elements/standard/multi_upload_field"
|
7
7
|
require "formalist/elements/standard/number_field"
|
8
8
|
require "formalist/elements/standard/radio_buttons"
|
9
|
+
require "formalist/elements/standard/rich_text_area"
|
10
|
+
require "formalist/elements/standard/search_selection_field"
|
11
|
+
require "formalist/elements/standard/search_multi_selection_field"
|
9
12
|
require "formalist/elements/standard/select_box"
|
10
13
|
require "formalist/elements/standard/selection_field"
|
14
|
+
require "formalist/elements/standard/tags_field"
|
11
15
|
require "formalist/elements/standard/text_area"
|
12
16
|
require "formalist/elements/standard/text_field"
|
13
17
|
require "formalist/elements/standard/upload_field"
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Formalist
|
2
|
+
class Form
|
3
|
+
class ValidityCheck
|
4
|
+
def call(form_ast)
|
5
|
+
form_ast.map { |node| visit(node) }.all?
|
6
|
+
end
|
7
|
+
alias_method :[], :call
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def visit(node)
|
12
|
+
name, nodes = node
|
13
|
+
|
14
|
+
send(:"visit_#{name}", nodes)
|
15
|
+
end
|
16
|
+
|
17
|
+
def visit_attr(node)
|
18
|
+
_name, _type, errors, _attributes, children = node
|
19
|
+
|
20
|
+
errors.empty? && children.map { |child| visit(child) }.all?
|
21
|
+
end
|
22
|
+
|
23
|
+
def visit_compound_field(node)
|
24
|
+
_type, _attributes, children = node
|
25
|
+
|
26
|
+
children.map { |child| visit(child) }.all?
|
27
|
+
end
|
28
|
+
|
29
|
+
def visit_field(node)
|
30
|
+
_name, _type, _input, errors, _attributes = node
|
31
|
+
|
32
|
+
errors.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def visit_group(node)
|
36
|
+
_type, _attributes, children = node
|
37
|
+
|
38
|
+
children.map { |child| visit(child) }.all?
|
39
|
+
end
|
40
|
+
|
41
|
+
def visit_many(node)
|
42
|
+
_name, _type, errors, _attributes, _child_template, children = node
|
43
|
+
|
44
|
+
errors.empty? && children.map { |child| visit(child) }.all?
|
45
|
+
end
|
46
|
+
|
47
|
+
def visit_section(node)
|
48
|
+
_name, _type, _attributes, children = node
|
49
|
+
|
50
|
+
children.map { |child| visit(child) }.all?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/formalist/form.rb
CHANGED
@@ -1,32 +1,64 @@
|
|
1
|
-
require "dry
|
1
|
+
require "dry/configurable"
|
2
|
+
require "dry/core/constants"
|
2
3
|
require "formalist/elements"
|
3
|
-
require "formalist/
|
4
|
-
require "formalist/element/permitted_children"
|
5
|
-
require "formalist/form/result"
|
4
|
+
require "formalist/definition"
|
6
5
|
|
7
6
|
module Formalist
|
8
7
|
class Form
|
9
8
|
extend Dry::Configurable
|
9
|
+
include Dry::Core::Constants
|
10
10
|
|
11
11
|
setting :elements_container, Elements
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
class << self
|
14
|
+
attr_reader :definition
|
15
|
+
|
16
|
+
def define(&block)
|
17
|
+
@definition = block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :elements
|
22
|
+
attr_reader :input
|
23
|
+
attr_reader :errors
|
24
|
+
attr_reader :dependencies
|
25
|
+
|
26
|
+
def initialize(elements: Undefined, input: {}, errors: {}, **dependencies)
|
27
|
+
@input = input
|
28
|
+
@errors = errors
|
29
|
+
|
30
|
+
@elements =
|
31
|
+
if elements == Undefined
|
32
|
+
Definition.new(self, self.class.config, &self.class.definition).elements
|
33
|
+
else
|
34
|
+
elements
|
35
|
+
end
|
36
|
+
|
37
|
+
@dependencies = dependencies
|
38
|
+
end
|
39
|
+
|
40
|
+
def fill(input: {}, errors: {})
|
41
|
+
return self if input == @input && errors = @errors
|
42
|
+
|
43
|
+
self.class.new(
|
44
|
+
elements: @elements.map { |element| element.fill(input: input, errors: errors) },
|
45
|
+
input: input,
|
46
|
+
errors: errors,
|
47
|
+
**@dependencies,
|
48
|
+
)
|
16
49
|
end
|
17
50
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
51
|
+
def with(**new_dependencies)
|
52
|
+
self.class.new(
|
53
|
+
elements: @elements,
|
54
|
+
input: @input,
|
55
|
+
errors: @errors,
|
56
|
+
**@dependencies.merge(new_dependencies)
|
57
|
+
)
|
24
58
|
end
|
25
59
|
|
26
|
-
|
27
|
-
|
28
|
-
elements = self.class.elements.map { |el| el.resolve(self) }
|
29
|
-
Result.new(input, messages, elements)
|
60
|
+
def to_ast
|
61
|
+
elements.map(&:to_ast)
|
30
62
|
end
|
31
63
|
end
|
32
64
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module Formalist
|
4
|
+
module RichText
|
5
|
+
|
6
|
+
# Our input data looks like this example, which consists of 3 elements:
|
7
|
+
#
|
8
|
+
# 1. A text line
|
9
|
+
# 2. embedded form data
|
10
|
+
# 3. Another text line
|
11
|
+
#
|
12
|
+
# [
|
13
|
+
# ["block",["unstyled","b14hd",[["inline",[[],"Before!"]]]]],
|
14
|
+
# ["block",["atomic","48b4f",[["entity",["formalist","1","IMMUTABLE",{"name":"image_with_caption","label":"Image with caption","data":{"image_id":"5678","caption":"Large panda"}},[["inline",[[],"¶"]]]]]]]],
|
15
|
+
# ["block",["unstyled","aivqi",[["inline",[[],"After!"]]]]]
|
16
|
+
# ]
|
17
|
+
#
|
18
|
+
# We want to intercept the embededed form data and transform them into full
|
19
|
+
# form ASTs, complete with validation messages.
|
20
|
+
|
21
|
+
class EmbeddedFormCompiler
|
22
|
+
attr_reader :embedded_forms
|
23
|
+
|
24
|
+
def initialize(embedded_form_collection)
|
25
|
+
@embedded_forms = embedded_form_collection
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(ast)
|
29
|
+
return ast if ast.nil?
|
30
|
+
|
31
|
+
ast = ast.is_a?(String) ? JSON.parse(ast) : ast
|
32
|
+
|
33
|
+
ast.map { |node| visit(node) }
|
34
|
+
end
|
35
|
+
alias_method :[], :call
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def visit(node)
|
40
|
+
name, nodes = node
|
41
|
+
|
42
|
+
handler = :"visit_#{name}"
|
43
|
+
|
44
|
+
if respond_to?(handler, true)
|
45
|
+
send(handler, nodes)
|
46
|
+
else
|
47
|
+
[name, nodes]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# We need to visit blocks in order to get to the formalist entities nested within them
|
52
|
+
def visit_block(node)
|
53
|
+
type, id, children = node
|
54
|
+
|
55
|
+
["block", [type, id, children.map { |child| visit(child) }]]
|
56
|
+
end
|
57
|
+
|
58
|
+
def visit_entity(node)
|
59
|
+
type, key, mutability, entity_data, children = node
|
60
|
+
|
61
|
+
return ["entity", node] unless type == "formalist"
|
62
|
+
|
63
|
+
embedded_form = embedded_forms[entity_data["name"]]
|
64
|
+
|
65
|
+
compiled_entity_data = entity_data.merge(
|
66
|
+
"label" => embedded_form.label,
|
67
|
+
"form" => prepare_form_ast(embedded_form, entity_data["data"])
|
68
|
+
)
|
69
|
+
|
70
|
+
["entity", [type, key, mutability, compiled_entity_data, children]]
|
71
|
+
end
|
72
|
+
|
73
|
+
def prepare_form_ast(embedded_form, data)
|
74
|
+
# Run the raw data through the validation schema
|
75
|
+
validation = embedded_form.schema.(data)
|
76
|
+
|
77
|
+
# And then through the embedded form's input processor (which may add
|
78
|
+
# extra system-generated information necessary for the form to render
|
79
|
+
# fully)
|
80
|
+
input = embedded_form.input_processor.(validation.to_h)
|
81
|
+
|
82
|
+
embedded_form.form.fill(input: input, errors: validation.messages).to_ast
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "dry-container"
|
2
|
+
require "formalist/rich_text/embedded_forms_container/registration"
|
3
|
+
|
4
|
+
module Formalist
|
5
|
+
module RichText
|
6
|
+
class EmbeddedFormsContainer
|
7
|
+
module Mixin
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
include ::Dry::Container::Mixin
|
11
|
+
include Methods
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.extended(base)
|
16
|
+
base.class_eval do
|
17
|
+
extend ::Dry::Container::Mixin
|
18
|
+
extend Methods
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Methods
|
23
|
+
def resolve(key)
|
24
|
+
super(key.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
def register(key, **attrs)
|
28
|
+
super(key.to_s, Registration.new(attrs))
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_h
|
32
|
+
keys.each_with_object({}) { |key, output|
|
33
|
+
output[key] = self[key].to_h
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
# TODO: methods to return filtered sets of registrations
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Formalist
|
2
|
+
module RichText
|
3
|
+
class EmbeddedFormsContainer
|
4
|
+
class Registration
|
5
|
+
DEFAULT_INPUT_PROCESSOR = -> input { input }.freeze
|
6
|
+
|
7
|
+
attr_reader :label
|
8
|
+
attr_reader :form
|
9
|
+
attr_reader :schema
|
10
|
+
attr_reader :input_processor
|
11
|
+
|
12
|
+
def initialize(label:, form:, schema:, input_processor: DEFAULT_INPUT_PROCESSOR)
|
13
|
+
@label = label
|
14
|
+
@form = form
|
15
|
+
@schema = schema
|
16
|
+
@input_processor = input_processor
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_h
|
20
|
+
{
|
21
|
+
label: label,
|
22
|
+
form: form,
|
23
|
+
schema: schema,
|
24
|
+
input_processor: input_processor,
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Formalist
|
2
|
+
module RichText
|
3
|
+
module Rendering
|
4
|
+
class EmbeddedFormRenderer
|
5
|
+
attr_reader :container
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(container = {}, **options)
|
9
|
+
@container = container
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(form_data)
|
14
|
+
type, data = form_data.values_at(:name, :data)
|
15
|
+
|
16
|
+
if container.key?(type)
|
17
|
+
container[type].(data, options)
|
18
|
+
else
|
19
|
+
""
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Formalist
|
2
|
+
module RichText
|
3
|
+
module Rendering
|
4
|
+
class HTMLCompiler
|
5
|
+
EMBEDDED_FORM_TYPE = "formalist".freeze
|
6
|
+
LIST_ITEM_TYPES = %w[unordered-list-item ordered-list-item].freeze
|
7
|
+
|
8
|
+
attr_reader :html_renderer
|
9
|
+
attr_reader :embedded_form_renderer
|
10
|
+
|
11
|
+
def initialize(html_renderer:, embedded_form_renderer:)
|
12
|
+
@html_renderer = html_renderer
|
13
|
+
@embedded_form_renderer = embedded_form_renderer
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(ast)
|
17
|
+
html_renderer.nodes(wrap_lists(ast)) do |node|
|
18
|
+
visit(node)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def visit(node)
|
25
|
+
type, content = node
|
26
|
+
|
27
|
+
send(:"visit_#{type}", content)
|
28
|
+
end
|
29
|
+
|
30
|
+
def visit_block(data)
|
31
|
+
type, key, children = data
|
32
|
+
|
33
|
+
html_renderer.block(type, key, wrap_lists(children)) do |child|
|
34
|
+
visit(child)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def visit_wrapper(data)
|
39
|
+
type, children = data
|
40
|
+
|
41
|
+
html_renderer.wrapper(type, children) do |child|
|
42
|
+
visit(child)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def visit_inline(data)
|
47
|
+
styles, text = data
|
48
|
+
|
49
|
+
html_renderer.inline(styles, text)
|
50
|
+
end
|
51
|
+
|
52
|
+
def visit_entity(data)
|
53
|
+
type, key, _mutability, data, children = data
|
54
|
+
|
55
|
+
# FIXME
|
56
|
+
# Temporary fix to handle data that comes through with keys as
|
57
|
+
# strings instead of symbols
|
58
|
+
data = data.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
59
|
+
|
60
|
+
if type == EMBEDDED_FORM_TYPE
|
61
|
+
embedded_form_renderer.(data)
|
62
|
+
else
|
63
|
+
html_renderer.entity(type, key, data, wrap_lists(children)) do |child|
|
64
|
+
visit(child)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def wrap_lists(nodes)
|
70
|
+
chunked = nodes.chunk do |node|
|
71
|
+
type, content = node
|
72
|
+
|
73
|
+
if type == "block"
|
74
|
+
content[0] # return the block's own type
|
75
|
+
else
|
76
|
+
type
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
chunked.inject([]) { |output, (type, chunk)|
|
81
|
+
if list_item?(type)
|
82
|
+
output << convert_to_wrapper_node(type, chunk)
|
83
|
+
else
|
84
|
+
# Flatten again by appending chunk onto array
|
85
|
+
output + chunk
|
86
|
+
end
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def convert_to_wrapper_node(type, children)
|
91
|
+
["wrapper", [type, children]]
|
92
|
+
end
|
93
|
+
|
94
|
+
def list_item?(type)
|
95
|
+
LIST_ITEM_TYPES.include?(type)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|