Skip to content
This repository was archived by the owner on Nov 1, 2017. It is now read-only.

Commit 69456fa

Browse files
committed
Merge pull request #7 from github/nested-items
Support nested items
2 parents 3b587d2 + a62378d commit 69456fa

File tree

3 files changed

+49
-36
lines changed

3 files changed

+49
-36
lines changed

lib/task_list/filter.rb

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,16 @@ class Filter < HTML::Pipeline::Filter
4747
(?=\s) # followed by whitespace
4848
/x
4949

50-
ListSelector = [
51-
# select UL/OL
52-
".//li[starts-with(text(),'[ ]')]/..",
53-
".//li[starts-with(text(),'[x]')]/..",
54-
# and those wrapped in Ps
55-
".//li/p[1][starts-with(text(),'[ ]')]/../..",
56-
".//li/p[1][starts-with(text(),'[x]')]/../.."
57-
].join(' | ').freeze
58-
59-
# Selects all LIs from a TaskList UL/OL
60-
ItemSelector = ".//li".freeze
50+
ListItemSelector = ".//li[task_list_item(.)]".freeze
51+
52+
class XPathSelectorFunction
53+
def self.task_list_item(nodes)
54+
nodes if nodes.text =~ ItemPattern
55+
end
56+
end
6157

6258
# Selects first P tag of an LI, if present
63-
ItemParaSelector = ".//p[1]".freeze
59+
ItemParaSelector = "./p[1]".freeze
6460

6561
# List of `TaskList::Item` objects that were recognized in the document.
6662
# This is available in the result hash as `:task_list_items`.
@@ -100,20 +96,22 @@ def render_task_list_item(item)
10096
#
10197
# Returns an Array of Nokogiri::XML::Element objects for ordered and
10298
# unordered lists.
103-
def task_lists
104-
doc.xpath(ListSelector)
99+
def list_items
100+
doc.xpath(ListItemSelector, XPathSelectorFunction)
105101
end
106102

107-
# Public: filters a Nokogiri::XML::Element ordered/unordered list, marking
108-
# up the list items in order to add behavior and include metadata.
103+
# Filters the source for task list items.
109104
#
110-
# Modifies the provided node.
105+
# Each item is wrapped in HTML to identify, style, and layer
106+
# useful behavior on top of.
107+
#
108+
# Modifications apply to the parsed document directly.
111109
#
112110
# Returns nothing.
113-
def filter_list(node)
114-
add_css_class(node, 'task-list')
111+
def filter!
112+
list_items.reverse.each do |li|
113+
add_css_class(li.parent, 'task-list')
115114

116-
node.xpath(ItemSelector).each do |li|
117115
outer, inner =
118116
if p = li.xpath(ItemParaSelector)[0]
119117
[p, p.inner_html]
@@ -122,28 +120,15 @@ def filter_list(node)
122120
end
123121
if match = (inner.chomp =~ ItemPattern && $1)
124122
item = TaskList::Item.new(match, inner)
125-
task_list_items << item
123+
# prepend because we're iterating in reverse
124+
task_list_items.unshift item
126125

127126
add_css_class(li, 'task-list-item')
128127
outer.inner_html = render_task_list_item(item)
129128
end
130129
end
131130
end
132131

133-
# Filters the source for task list items.
134-
#
135-
# Each item is wrapped in HTML to identify, style, and layer
136-
# useful behavior on top of.
137-
#
138-
# Modifications apply to the parsed document directly.
139-
#
140-
# Returns nothing.
141-
def filter!
142-
task_lists.each do |node|
143-
filter_list node
144-
end
145-
end
146-
147132
def call
148133
filter!
149134
doc
@@ -153,6 +138,7 @@ def call
153138
# names.
154139
def add_css_class(node, *new_class_names)
155140
class_names = (node['class'] || '').split(' ')
141+
return if new_class_names.all? { |klass| class_names.include?(klass) }
156142
class_names.concat(new_class_names)
157143
node['class'] = class_names.uniq.join(' ')
158144
end

lib/task_list/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
class TaskList
2-
VERSION = [0, 2, 1].join('.')
2+
VERSION = [0, 3, 0].join('.')
33
end

test/task_list/filter_test.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,33 @@ def test_handles_encoding_correctly
7878
assert_equal unicode, item.text.strip
7979
end
8080

81+
def test_handles_nested_items
82+
text = <<-md
83+
- [ ] one
84+
- [ ] one.one
85+
md
86+
assert item = filter(text)[:output].css('.task-list-item .task-list-item').pop
87+
end
88+
89+
def test_handles_complicated_nested_items
90+
text = <<-md
91+
- [ ] one
92+
- [ ] one.one
93+
- [x] one.two
94+
- [ ] one.two.one
95+
- [ ] one.two.two
96+
- [ ] one.three
97+
- [ ] one.four
98+
- [ ] two
99+
- [x] two.one
100+
- [ ] two.two
101+
- [ ] three
102+
md
103+
104+
assert_equal 6 + 2, filter(text)[:output].css('.task-list-item .task-list-item').size
105+
assert_equal 2, filter(text)[:output].css('.task-list-item .task-list-item .task-list-item').size
106+
end
107+
81108
protected
82109

83110
def filter(input, context = @context, result = nil)

0 commit comments

Comments
 (0)