formalist 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|