express_admin 1.4.8 → 1.4.9
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 +4 -4
- data/app/assets/javascripts/ace/ace.js +18281 -0
- data/app/assets/javascripts/ace/mode-ruby.js +843 -0
- data/app/assets/javascripts/ace/theme-github.js +105 -0
- data/app/assets/javascripts/ace/worker-html.js +11527 -0
- data/app/assets/javascripts/express_admin/admin.js.coffee +36 -0
- data/app/assets/javascripts/express_admin.js +5 -0
- data/app/components/express_admin/nav_bar_actions.rb +2 -0
- data/app/components/express_admin/smart_form.rb +9 -1
- data/app/components/express_admin/smart_table.rb +36 -26
- data/lib/express_admin/engine.rb +2 -0
- data/lib/express_admin/standard_actions.rb +5 -5
- data/lib/express_admin/version.rb +1 -1
- data/test/dummy/test/components/definition_list_test.rb +13 -13
- data/test/dummy/test/components/icon_link_test.rb +52 -52
- data/test/dummy/test/components/icon_test.rb +2 -2
- data/test/dummy/test/components/mega_menu_test.rb +2 -2
- data/test/dummy/test/components/module_sidebar_test.rb +3 -3
- data/test/dummy/test/components/smart_table_test.rb +11 -12
- data/test/test_helper.rb +1 -1
- data/vendor/gems/express_templates/express_templates-0.9.8.gem +0 -0
- data/vendor/gems/express_templates/lib/express_templates/components/forms/select.rb +8 -4
- data/vendor/gems/express_templates/lib/express_templates/components/tree_for.rb +10 -1
- data/vendor/gems/express_templates/lib/express_templates/version.rb +1 -1
- data/vendor/gems/express_templates/test/dummy/log/test.log +321 -0
- metadata +23 -4
@@ -1,3 +1,37 @@
|
|
1
|
+
class AceInput
|
2
|
+
constructor: (editor)->
|
3
|
+
@editor = ace.edit(editor)
|
4
|
+
@editor.$blockScrolling = Infinity
|
5
|
+
@session = @editor.getSession()
|
6
|
+
@renderer = @editor.renderer
|
7
|
+
@textarea = $("##{$(editor).data('target')}")
|
8
|
+
@setOptions()
|
9
|
+
@updateMode()
|
10
|
+
@updateTheme()
|
11
|
+
@bindTextarea()
|
12
|
+
@editor.setFontSize "16px"
|
13
|
+
|
14
|
+
setOptions: =>
|
15
|
+
@renderer.setShowPrintMargin false
|
16
|
+
@renderer.setHScrollBarAlwaysVisible false
|
17
|
+
@session.setUseWorker false
|
18
|
+
@session.setTabSize 2
|
19
|
+
@session.setUseSoftTabs true
|
20
|
+
@session.setFoldStyle "markbeginend"
|
21
|
+
|
22
|
+
updateMode: =>
|
23
|
+
mode = require("ace/mode/ruby").Mode
|
24
|
+
@session.setMode new mode()
|
25
|
+
|
26
|
+
updateTheme: =>
|
27
|
+
@editor.setTheme require("ace/theme/github")
|
28
|
+
|
29
|
+
bindTextarea: =>
|
30
|
+
ace = @
|
31
|
+
ace.session.setValue ace.textarea.val()
|
32
|
+
ace.session.on "change", ->
|
33
|
+
ace.textarea.val ace.session.getValue()
|
34
|
+
|
1
35
|
$(document).ready ->
|
2
36
|
$('.select2').select2()
|
3
37
|
# Table Row as links
|
@@ -19,6 +53,8 @@ $(document).ready ->
|
|
19
53
|
e.preventDefault()
|
20
54
|
$('a.close-reveal-modal').trigger 'click'
|
21
55
|
return
|
56
|
+
$('.ace-input').each (index)->
|
57
|
+
editor = new AceInput(this)
|
22
58
|
|
23
59
|
String::repeat = (num) ->
|
24
60
|
new Array(num + 1).join this
|
@@ -1,6 +1,11 @@
|
|
1
1
|
//= require jquery
|
2
2
|
//= require jquery_ujs
|
3
|
+
//= require jquery-ui/sortable
|
3
4
|
//= require jquery.loadingdotdotdot
|
4
5
|
//= require tinymce-jquery
|
5
6
|
//= require select2
|
7
|
+
//= require ace/ace
|
8
|
+
//= require ace/worker-html
|
9
|
+
//= require ace/mode-ruby
|
10
|
+
//= require ace/theme-github
|
6
11
|
//= require_tree .
|
@@ -48,7 +48,15 @@ module ExpressAdmin
|
|
48
48
|
select(attrib.name.to_sym, options: config["#{relation}_collection".to_sym], select2: true)
|
49
49
|
else
|
50
50
|
if field_type == 'text_area'
|
51
|
-
|
51
|
+
if attrib.name == 'definition'
|
52
|
+
# TODO allow other fields aside from layout.definition
|
53
|
+
base_styles = "position: relative; height: 300px;"
|
54
|
+
target = [attributes[:class].to_a.last, attrib.name].join("_")
|
55
|
+
textarea attrib.name.to_sym, rows: 10, class: "hide", hidden: true
|
56
|
+
content_tag(:div, '', id: "ace_#{attrib.name}", class: "ace-input", style: base_styles, data: { target: target })
|
57
|
+
else
|
58
|
+
textarea attrib.name.to_sym, rows: 10
|
59
|
+
end
|
52
60
|
else
|
53
61
|
self.send((field_type_substitutions[field_type] || field_type), attrib.name.to_sym)
|
54
62
|
end
|
@@ -12,14 +12,24 @@ module ExpressAdmin
|
|
12
12
|
has_option :show_actions, 'Set to true if table has actions for each row'
|
13
13
|
has_option :row_class, 'Add a class to each table row'
|
14
14
|
|
15
|
-
|
16
15
|
column_defs = {}
|
17
16
|
column_defs[:array] = {description: "List of fields to include in the table as columns",
|
18
|
-
options: -> {resource.columns.map(&:name)} }
|
17
|
+
options: -> { resource.columns.map(&:name)} }
|
19
18
|
column_defs[:hash] = {description: "Hash of column names (titles) and how to calculate the cell values."}
|
20
19
|
|
21
20
|
has_option :columns, 'Specify the columns. May provide as a hash with values used to provide cell values as a proc.',
|
22
|
-
type: [:array, :hash]
|
21
|
+
type: [:array, :hash],
|
22
|
+
values: -> (*) {
|
23
|
+
options = resource_class.columns.map(&:name)
|
24
|
+
resource_class.columns.map(&:name).each do |name|
|
25
|
+
options << if name.match(/(\w+)_at$/)
|
26
|
+
"#{name}_in_words"
|
27
|
+
else
|
28
|
+
"#{name}_link"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
options = options + resource_class.instance_methods.grep(/_count$/).map(&:to_s)
|
32
|
+
}
|
23
33
|
|
24
34
|
contains -> {
|
25
35
|
thead {
|
@@ -100,29 +110,29 @@ module ExpressAdmin
|
|
100
110
|
end
|
101
111
|
|
102
112
|
def cell_value(item, accessor)
|
103
|
-
if accessor.respond_to?(:call)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
113
|
+
value = if accessor.respond_to?(:call)
|
114
|
+
begin
|
115
|
+
accessor.call(item).html_safe
|
116
|
+
rescue
|
117
|
+
'Error: '+$!.to_s
|
118
|
+
end
|
119
|
+
elsif attrib = accessor.to_s.match(/(\w+)_link$/).try(:[], 1)
|
120
|
+
# TODO: only works with non-namespaced routes
|
121
|
+
helpers.link_to item.send(attrib), resource_path(item)
|
122
|
+
elsif attrib = accessor.to_s.match(/(\w+)_in_words/).try(:[], 1)
|
123
|
+
item.send(attrib) ? (helpers.time_ago_in_words(item.send(attrib))+' ago') : 'never'
|
124
|
+
else
|
125
|
+
if relation_name = accessor.to_s.match(/(.*)_id$/).try(:[], 1)
|
126
|
+
reflection = resource_class.reflect_on_association(relation_name.to_sym)
|
127
|
+
end
|
128
|
+
|
129
|
+
if reflection
|
130
|
+
relation = item.send(relation_name)
|
131
|
+
relation.try(:name) || relation.to_s
|
132
|
+
else
|
133
|
+
item.send(accessor)
|
134
|
+
end
|
135
|
+
end
|
126
136
|
current_arbre_element.add_child value
|
127
137
|
end
|
128
138
|
|
data/lib/express_admin/engine.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'express_admin/menu'
|
2
2
|
require 'express_admin/version'
|
3
|
+
require 'express_admin/standard_actions'
|
3
4
|
require 'express_templates'
|
4
5
|
require 'jquery-rails'
|
6
|
+
require 'jquery-ui-rails'
|
5
7
|
require 'select2-rails'
|
6
8
|
require 'foundation_apps_styles'
|
7
9
|
require 'bourbon'
|
@@ -220,15 +220,15 @@ module ExpressAdmin
|
|
220
220
|
# TODO: optimize
|
221
221
|
parent_id = extract_path_info_from_routes["#{parent_name}_id".to_sym]
|
222
222
|
current_parent = "current_#{parent_name}".to_sym
|
223
|
-
unless self.
|
223
|
+
unless self.methods.include?(current_parent)
|
224
224
|
if previous_parent.nil?
|
225
225
|
self.class_eval do
|
226
226
|
define_method(current_parent) do
|
227
227
|
parent_class = parent_module_name.constantize
|
228
|
-
current_class_name = parent_name.
|
228
|
+
current_class_name = parent_name.camelize
|
229
229
|
current_class = parent_class.const_defined?(current_class_name) ?
|
230
230
|
parent_class.const_get(current_class_name) :
|
231
|
-
"::#{parent_name.
|
231
|
+
"::#{parent_name.camelize}".constantize
|
232
232
|
current_class.find(parent_id)
|
233
233
|
end
|
234
234
|
end
|
@@ -266,13 +266,13 @@ module ExpressAdmin
|
|
266
266
|
next unless engine_route
|
267
267
|
path_for_engine = request.path.gsub(%r(^#{engine_route.path.spec.to_s}), "")
|
268
268
|
begin
|
269
|
-
recognized_path = engine_instance.routes.recognize_path(path_for_engine
|
269
|
+
recognized_path = engine_instance.routes.recognize_path(path_for_engine)
|
270
270
|
rescue ActionController::RoutingError => e
|
271
271
|
end
|
272
272
|
end
|
273
273
|
if recognized_path.nil?
|
274
274
|
begin
|
275
|
-
recognized_path = Rails.application.routes.recognize_path(request.path
|
275
|
+
recognized_path = Rails.application.routes.recognize_path(request.path)
|
276
276
|
rescue ActionController::RoutingError => e
|
277
277
|
end
|
278
278
|
end
|
@@ -5,7 +5,7 @@ module ExpressAdmin
|
|
5
5
|
class DefinitionListTest < ActiveSupport::TestCase
|
6
6
|
|
7
7
|
def assigns
|
8
|
-
|
8
|
+
{list_types: list_types}
|
9
9
|
end
|
10
10
|
|
11
11
|
def helpers
|
@@ -13,21 +13,21 @@ module ExpressAdmin
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def list_types
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
16
|
+
@list_types ||= OpenStruct.new(
|
17
|
+
array: ["field1", "field2"],
|
18
|
+
hash: {term1: "def1",
|
19
|
+
term2: "def2",
|
20
|
+
term3: "def3"})
|
21
|
+
end
|
22
22
|
|
23
23
|
def deflist(*args)
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
arbre {
|
25
|
+
definition_list(:deflist, *args)
|
26
|
+
}.to_s
|
27
27
|
end
|
28
28
|
|
29
29
|
test "accepts array as input" do
|
30
|
-
|
30
|
+
assert deflist(list_types[:array])
|
31
31
|
end
|
32
32
|
|
33
33
|
test "accepts hash as input" do
|
@@ -64,8 +64,8 @@ HTML
|
|
64
64
|
|
65
65
|
test "definition_list renders correct markup with array input" do
|
66
66
|
assert_equal DEFLIST_MARKUP_ARR, deflist(list_types[:array])
|
67
|
-
end
|
67
|
+
end
|
68
68
|
|
69
69
|
end
|
70
70
|
|
71
|
-
end
|
71
|
+
end
|
@@ -4,74 +4,74 @@ module ExpressAdmin
|
|
4
4
|
|
5
5
|
class IconLinkTest < ActiveSupport::TestCase
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
7
|
+
def assigns
|
8
|
+
{resource: resource}
|
9
|
+
end
|
10
|
+
|
11
|
+
def resource
|
12
|
+
@resource ||= OpenStruct.new(
|
13
|
+
text: 'Beer',
|
14
|
+
title: 'beer icon',
|
15
|
+
target: '_blank',
|
16
|
+
right: true,
|
17
|
+
delete: true,
|
18
|
+
confirm: true,
|
19
|
+
href: 'http://something.com'
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def helpers
|
24
24
|
mock_action_view(assigns)
|
25
25
|
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
def rendered_icon_link(*args)
|
28
|
+
arbre {
|
29
|
+
icon_link(:beer, *args)
|
30
|
+
}.to_s
|
31
|
+
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
test "renders" do
|
34
|
+
assert rendered_icon_link
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
test "icon link href default is set to #" do
|
38
|
+
assert_match /href="#"/, rendered_icon_link
|
39
|
+
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
test "icon-link target set to blank" do
|
42
|
+
# binding.pry
|
43
|
+
assert_match /target="_blank"/, rendered_icon_link(target: "#{resource[:target]}")
|
44
|
+
end
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
test "delete attribute is true" do
|
47
|
+
assert_match /data-delete="true"/, rendered_icon_link(delete: resource[:delete])
|
48
|
+
end
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
|
50
|
+
test "confirm attribute is true" do
|
51
|
+
assert_match /data-confirm="true"/, rendered_icon_link(confirm: resource[:confirm])
|
52
|
+
end
|
53
53
|
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
test "icon link has title set" do
|
55
|
+
assert_match /title="beer icon"/, rendered_icon_link(title: "#{resource[:title]}")
|
56
|
+
end
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
test "icon link has accompanying text" do
|
59
|
+
assert_match /i>\nBeer<\/a>/, rendered_icon_link(text: "#{resource[:text]}")
|
60
|
+
end
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
test "icon link has link set" do
|
63
|
+
assert_match /href="#{resource[:href]}"/, rendered_icon_link(href: "#{resource[:href]}")
|
64
|
+
end
|
65
65
|
|
66
|
-
|
66
|
+
MARKUP_RIGHT = <<-HTML
|
67
67
|
<a class="icon-link" href="#">
|
68
68
|
Beer <i class="icon ion-beer"></i>
|
69
69
|
</a>
|
70
70
|
HTML
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
test "if icon-link is set to right" do
|
73
|
+
assert_equal MARKUP_RIGHT, rendered_icon_link(text: "#{resource[:text]}", right: "#{resource[:right]}")
|
74
|
+
end
|
75
75
|
|
76
76
|
end
|
77
|
-
end
|
77
|
+
end
|
@@ -29,12 +29,12 @@ module ExpressAdmin
|
|
29
29
|
|
30
30
|
test "renders" do
|
31
31
|
assert rendered_mega_menu
|
32
|
-
end
|
32
|
+
end
|
33
33
|
|
34
34
|
test "links menu to eval'd path" do
|
35
35
|
assert_match /href="evaled_path"/, rendered_mega_menu
|
36
36
|
assert_match /href="some_path"/, rendered_mega_menu
|
37
|
-
end
|
37
|
+
end
|
38
38
|
|
39
39
|
test "replaces whitespace in menu title to underscore for icon class" do
|
40
40
|
assert_match /icon-express_big_menu/, rendered_mega_menu
|
@@ -3,7 +3,7 @@ require 'test_helper'
|
|
3
3
|
module Components
|
4
4
|
|
5
5
|
class ModuleSidebarTest < ActiveSupport::TestCase
|
6
|
-
|
6
|
+
|
7
7
|
class MenuItem
|
8
8
|
attr_accessor :title, :path, :items
|
9
9
|
|
@@ -21,7 +21,7 @@ module Components
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def assigns
|
24
|
-
{ current_menu: current_menu,
|
24
|
+
{ current_menu: current_menu,
|
25
25
|
current_menu_name: current_menu_name,
|
26
26
|
foo_path: 'foo',
|
27
27
|
bar_path: 'bar',
|
@@ -53,4 +53,4 @@ module Components
|
|
53
53
|
end
|
54
54
|
|
55
55
|
end
|
56
|
-
end
|
56
|
+
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
module Components
|
4
|
-
|
5
4
|
class SmartTableTest < ActiveSupport::TestCase
|
6
5
|
|
7
6
|
fixtures :widgets, :categories
|
@@ -24,17 +23,17 @@ module Components
|
|
24
23
|
}
|
25
24
|
end
|
26
25
|
|
27
|
-
test
|
26
|
+
test 'should have hidden columns' do
|
28
27
|
assert_match 'more-columns-indicator', compiled_widget_table
|
29
28
|
assert_no_match 'column7', compiled_widget_table
|
30
29
|
end
|
31
30
|
|
32
|
-
test
|
31
|
+
test 'iterates over collection setting table row id correctly' do
|
33
32
|
assert_match 'widget:298486374', compiled_widget_table
|
34
33
|
assert_match 'widget:980190962', compiled_widget_table
|
35
34
|
end
|
36
35
|
|
37
|
-
test
|
36
|
+
test 'renders cell contents' do
|
38
37
|
assert_match '<td class="column2">Hammer</td>', compiled_widget_table
|
39
38
|
end
|
40
39
|
|
@@ -43,24 +42,24 @@ module Components
|
|
43
42
|
# assert_match /\{\{\(widget.column3\).to_s.truncate\(27\)\}\}/, compiled_widget_table
|
44
43
|
# end
|
45
44
|
|
46
|
-
test
|
45
|
+
test 'table cell shows related item name or display name' do
|
47
46
|
# note this will move to a helper that will intelligently look
|
48
47
|
# for decorated methods such as name or display_name
|
49
48
|
assert_match '<td class="category_id">Toys</td>', compiled_widget_table
|
50
49
|
assert_match '<td class="category_id">Tools</td>', compiled_widget_table
|
51
50
|
end
|
52
51
|
|
53
|
-
test
|
52
|
+
test 'table displays only columns specified if columns option provided' do
|
54
53
|
compiled = compiled_widget_table(columns: [:column3, :column4])
|
55
54
|
assert_match 'column3', compiled
|
56
|
-
assert_match '
|
55
|
+
assert_match 'column4', compiled
|
57
56
|
refute_match 'column2', compiled
|
58
57
|
refute_match 'category_id', compiled
|
59
58
|
refute_match 'column5', compiled
|
60
59
|
refute_match 'column6', compiled
|
61
60
|
end
|
62
61
|
|
63
|
-
test
|
62
|
+
test 'table column titles may be customized' do
|
64
63
|
compiled = compiled_widget_table(columns: {"Column3 is the Best" => :column3})
|
65
64
|
assert_match /<th class="column3">Column3 is the Best/, compiled
|
66
65
|
end
|
@@ -78,15 +77,15 @@ module Components
|
|
78
77
|
assert_match 'Error', compiled_widget_table_with_proc_column
|
79
78
|
end
|
80
79
|
|
81
|
-
test
|
80
|
+
test 'table cell contains result of proc.call if no exception is raised' do
|
82
81
|
assert_match 'LEGO', compiled_widget_table_with_proc_column
|
83
82
|
end
|
84
83
|
|
85
|
-
test
|
84
|
+
test 'table cell class is valid when proc is used' do
|
86
85
|
assert_match 'class="this_column_will_error"', compiled_widget_table_with_proc_column
|
87
86
|
end
|
88
87
|
|
89
|
-
test
|
88
|
+
test 'attribute accessor appended with _link generates a link' do
|
90
89
|
fragment = arbre {
|
91
90
|
smart_table(:widgets, columns: {
|
92
91
|
'A link column' => :column3_link
|
@@ -95,7 +94,7 @@ module Components
|
|
95
94
|
assert_match /column3.*href="\/widgets\/(\d+)/, fragment
|
96
95
|
end
|
97
96
|
|
98
|
-
test
|
97
|
+
test 'timestamp accessor appeneded with _in_words generates code that uses time_ago_in_words' do
|
99
98
|
fragment = arbre {
|
100
99
|
smart_table(:widgets, columns: {
|
101
100
|
'Created' => :created_at_in_words
|
data/test/test_helper.rb
CHANGED
Binary file
|
@@ -60,10 +60,14 @@ module ExpressTemplates
|
|
60
60
|
|
61
61
|
def options_from_supplied_or_field_values
|
62
62
|
if select_options_supplied?
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
63
|
+
supplied_options = use_supplied_options
|
64
|
+
if supplied_options.respond_to?(:map)
|
65
|
+
helpers.options_for_select(
|
66
|
+
normalize_for_helper(supplied_options),
|
67
|
+
selected_value)
|
68
|
+
else
|
69
|
+
supplied_options
|
70
|
+
end
|
67
71
|
else
|
68
72
|
generate_options_from_field_values
|
69
73
|
end
|
@@ -42,16 +42,25 @@ module ExpressTemplates
|
|
42
42
|
tag :ul
|
43
43
|
|
44
44
|
has_attributes :class => 'tree'
|
45
|
+
has_option :root, "Root of the tree. Defaults to collection with the same as the id.", type: :proc
|
45
46
|
|
46
47
|
contains -> (&customize_block) {
|
47
48
|
@customize_block = customize_block
|
48
|
-
list_items(
|
49
|
+
list_items(root_node)
|
49
50
|
}
|
50
51
|
|
51
52
|
before_build -> {
|
52
53
|
add_class config[:id]
|
53
54
|
}
|
54
55
|
|
56
|
+
def root_node
|
57
|
+
if config[:root] && config[:root].respond_to?(:call)
|
58
|
+
config[:root].call
|
59
|
+
else
|
60
|
+
send(config[:id])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
55
64
|
def list_items(nodes)
|
56
65
|
nodes.each do |node|
|
57
66
|
list_item(node)
|