# encoding: utf-8
require 'html/pipeline'
require 'task_list'
class TaskList
# Returns a `Nokogiri::DocumentFragment` object.
def self.filter(*args)
Filter.call(*args)
end
# TaskList filter replaces task list item markers (`[ ]` and `[x]`) with
# checkboxes, marked up with metadata and behavior.
#
# This should be run on the HTML generated by the Markdown filter, after the
# SanitizationFilter.
#
# Syntax
# ------
#
# Task list items must be in a list format:
#
# ```
# - [ ] incomplete
# - [x] complete
# ```
#
# Results
# -------
#
# The following keys are written to the result hash:
# :task_list_items - An array of TaskList::Item objects.
class Filter < HTML::Pipeline::Filter
Incomplete = "[ ]".freeze
Complete = "[x]".freeze
IncompletePattern = /\[[[:space:]]\]/.freeze # matches all whitespace
CompletePattern = /\[[xX]\]/.freeze # matches any capitalization
# All valid checkbox patterns.
# Useful for overriding ItemPattern wih custom formats.
CheckboxPatterns = /#{CompletePattern}|#{IncompletePattern}/
# Pattern used to identify all task list items.
# Useful when you need iterate over all items.
ItemPattern = /
^
(?:\s*[-+*]|(?:\d+\.))? # optional list prefix
\s* # optional whitespace prefix
(#{CheckboxPatterns}) # checkbox
(?=\s) # followed by whitespace
/x
# List of `TaskList::Item` objects that were recognized in the document.
# This is available in the result hash as `:task_list_items`.
#
# Returns an Array of TaskList::Item objects.
def task_list_items
result[:task_list_items] ||= []
end
# Computes attributes for the item input.
#
# Returns an String of HTML attributes.
def checkbox_attributes(item)
'checked="checked"' if item.complete?
end
# Renders the item checkbox in a span including the item state.
#
# Returns an HTML-safe String.
def render_item_checkbox(item)
%()
end
# Public: Marks up the task list item checkbox with metadata and behavior.
#
# NOTE: produces a string that, when assigned to a Node's `inner_html`,
# will corrupt the string contents' encodings. Instead, we parse the
# rendered HTML and explicitly set its encoding so that assignment will
# not change the encodings.
#
# See [this pull](https://github.com/github/github/pull/8505) for details.
#
# Returns the marked up task list item Nokogiri::XML::NodeSet object.
def render_task_list_item(item)
Nokogiri::HTML.fragment \
item.source.sub(ItemPattern, render_item_checkbox(item)), 'utf-8'
end
# Filters the source for task list items.
#
# Each item is wrapped in HTML to identify, style, and layer
# useful behavior on top of.
#
# Modifications apply to the parsed document directly.
#
# Returns nothing.
def filter!
doc.xpath(".//li").reverse.each do |li|
container = li.xpath("./p[1]")[0] || li
next if not container.inner_html =~ ItemPattern
item = TaskList::Item.new($1, container.inner_html)
task_list_items.unshift item
container.inner_html = render_task_list_item(item)
li.parent.add_class('task-list')
li.add_class('task-list-item')
end
end
def call
filter!
doc
end
end
end