# 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