deckar01-task_list-2.0.0/ 0000755 0000041 0000041 00000000000 13155253103 015124 5 ustar www-data www-data deckar01-task_list-2.0.0/Rakefile 0000644 0000041 0000041 00000000276 13155253103 016576 0 ustar www-data www-data require "bundler/gem_tasks" require "rake/testtask" task :default => :test Rake::TestTask.new do |t| t.libs << "lib" t.test_files = FileList['test/**/*_test.rb'] t.verbose = true end deckar01-task_list-2.0.0/task_list.gemspec 0000644 0000041 0000041 00000002511 13155253103 020465 0 ustar www-data www-data # -*- encoding: utf-8 -*- lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'task_list/version' Gem::Specification.new do |gem| gem.name = "deckar01-task_list" gem.version = TaskList::VERSION gem.authors = ["Jared Deckard", "Matt Todd"] gem.email = ["jared.deckard@gmail.com"] gem.description = %q{Markdown TaskList components} gem.summary = %q{Markdown TaskList components} gem.files = `git ls-files`.split($/) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ["lib"] gem.required_ruby_version = ">= 2.1.0" gem.add_dependency "activesupport", "~> 4.0" if RUBY_VERSION < '2.2.2' gem.add_dependency "nokogiri", "~> 1.6.0" if RUBY_VERSION < '2.1.0' gem.add_dependency "html-pipeline" gem.add_development_dependency "github-markdown" gem.add_development_dependency "rake" gem.add_development_dependency "coffee-script" gem.add_development_dependency "json" gem.add_development_dependency "rack", "~> 1.0" if RUBY_VERSION < '2.2.2' gem.add_development_dependency "rack" if RUBY_VERSION >= '2.2.2' gem.add_development_dependency "sprockets" gem.add_development_dependency "minitest", "~> 5.3.2" end deckar01-task_list-2.0.0/Gemfile 0000644 0000041 0000041 00000000046 13155253103 016417 0 ustar www-data www-data source "https://rubygems.org" gemspec deckar01-task_list-2.0.0/script/ 0000755 0000041 0000041 00000000000 13155253103 016430 5 ustar www-data www-data deckar01-task_list-2.0.0/script/testsuite 0000755 0000041 0000041 00000000462 13155253103 020411 0 ustar www-data www-data #!/usr/bin/env ruby root = File.expand_path("../..", __FILE__) Dir.chdir root pid = fork do $stderr.reopen "/dev/null" # silence WEBrick output exec 'bundle', 'exec', 'rackup', '-p', '4018' end sleep 1 status = system('npm', 'run', 'qunit') Process.kill 'SIGINT', pid Process.wait pid exit status deckar01-task_list-2.0.0/script/bootstrap 0000755 0000041 0000041 00000000240 13155253103 020367 0 ustar www-data www-data #!/bin/sh set -e 0 if ! bundle check 1>/dev/null 2>&1; then bundle install --no-color --binstubs --path vendor/gems fi npm install bower install --no-color deckar01-task_list-2.0.0/script/cibuild 0000755 0000041 0000041 00000000164 13155253103 017772 0 ustar www-data www-data #!/bin/sh -e # Usage: script/cibuild # CI build script. npm run lint ./script/testsuite 4018 bundle exec rake test deckar01-task_list-2.0.0/.travis.yml 0000644 0000041 0000041 00000000602 13155253103 017233 0 ustar www-data www-data language: ruby sudo: false install: - rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION - ./script/bootstrap script: ./script/cibuild rvm: - 1.9.3 - 2.0 - 2.1 - 2.2 env: - TRAVIS_NODE_VERSION="7" notifications: email: false deckar01-task_list-2.0.0/lib/ 0000755 0000041 0000041 00000000000 13155253103 015672 5 ustar www-data www-data deckar01-task_list-2.0.0/lib/task_list.rb 0000644 0000041 0000041 00000001755 13155253103 020224 0 ustar www-data www-data require 'task_list/summary' require 'task_list/version' # encoding: utf-8 class TaskList attr_reader :record # `record` is the resource with the Markdown source text with task list items # following this syntax: # # - [ ] a task list item # - [ ] another item # - [x] a completed item # def initialize(record) @record = record end # Public: return the TaskList::Summary for this task list. # # Returns a TaskList::Summary. def summary @summary ||= TaskList::Summary.new(record.task_list_items) end class Item < Struct.new(:checkbox_text, :source) Complete = /\[[xX]\]/.freeze # see TaskList::Filter # Public: Check if a task list is complete. # # Examples # # Item.new(checkbox_text: "- [x]").complete? # # => true # # Item.new(checkbox_text: "- [ ]").complete? # # => false # # Returns true for checked list, false otherwise def complete? !!(checkbox_text =~ Complete) end end end deckar01-task_list-2.0.0/lib/task_list/ 0000755 0000041 0000041 00000000000 13155253103 017667 5 ustar www-data www-data deckar01-task_list-2.0.0/lib/task_list/filter.rb 0000644 0000041 0000041 00000010735 13155253103 021507 0 ustar www-data www-data # 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 # 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 ( # checkbox #{CompletePattern}| #{IncompletePattern} ) (?=\s) # followed by whitespace /x ListItemSelector = ".//li[task_list_item(.)]".freeze class XPathSelectorFunction def self.task_list_item(nodes) nodes.select { |node| node.text =~ ItemPattern } end end # Selects first P tag of an LI, if present ItemParaSelector = "./p[1]".freeze # 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 # 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 # Public: Select all task list items within `container`. # # Returns an Array of Nokogiri::XML::Element objects for ordered and # unordered lists. The container can either be the entire document (as # returned by `#doc`) or an Element object. def list_items(container) container.xpath(ListItemSelector, XPathSelectorFunction) 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! list_items(doc).reverse.each do |li| next if list_items(li.parent).empty? add_css_class(li.parent, 'task-list') outer, inner = if p = li.xpath(ItemParaSelector)[0] [p, p.inner_html] else [li, li.inner_html] end if match = (inner.chomp =~ ItemPattern && $1) item = TaskList::Item.new(match, inner) # prepend because we're iterating in reverse task_list_items.unshift item add_css_class(li, 'task-list-item') outer.inner_html = render_task_list_item(item) end end end def call filter! doc end # Private: adds a CSS class name to a node, respecting existing class # names. def add_css_class(node, *new_class_names) class_names = (node['class'] || '').split(' ') return if new_class_names.all? { |klass| class_names.include?(klass) } class_names.concat(new_class_names) node['class'] = class_names.uniq.join(' ') end end end deckar01-task_list-2.0.0/lib/task_list/version.rb 0000644 0000041 0000041 00000000063 13155253103 021700 0 ustar www-data www-data class TaskList VERSION = [2, 0, 0].join('.') end deckar01-task_list-2.0.0/lib/task_list/railtie.rb 0000644 0000041 0000041 00000000657 13155253103 021655 0 ustar www-data www-data class TaskList def self.root_path @root_path ||= Pathname.new(File.expand_path("../../../", __FILE__)) end def self.asset_paths @paths ||= Dir[root_path.join("app/assets/*")] end if defined? ::Rails::Railtie class Railtie < ::Rails::Railtie initializer "task_list" do |app| TaskList.asset_paths.each do |path| app.config.assets.paths << path end end end end end deckar01-task_list-2.0.0/lib/task_list/summary.rb 0000644 0000041 0000041 00000001347 13155253103 021716 0 ustar www-data www-data # encoding: utf-8 require 'html/pipeline' require 'task_list' class TaskList # Provides a summary of provided TaskList `items`. # # `items` is an Array of TaskList::Item objects. class Summary < Struct.new(:items) # Public: returns true if there are any TaskList::Item objects. def items? item_count > 0 end # Public: returns the number of TaskList::Item objects. def item_count items.size end # Public: returns the number of complete TaskList::Item objects. def complete_count items.select{ |i| i.complete? }.size end # Public: returns the number of incomplete TaskList::Item objects. def incomplete_count items.select{ |i| !i.complete? }.size end end end deckar01-task_list-2.0.0/test/ 0000755 0000041 0000041 00000000000 13155253103 016103 5 ustar www-data www-data deckar01-task_list-2.0.0/test/task_list_test.rb 0000644 0000041 0000041 00000001345 13155253103 021467 0 ustar www-data www-data # encoding: utf-8 require File.expand_path('../test_helper', __FILE__) require 'task_list' require 'task_list/filter' class TaskListTest < Minitest::Test class Record < Struct.new(:body) def task_list_items [] end end def test_has_summary assert summary = task_list("- [ ] one").summary, "summary expected" assert_kind_of TaskList::Summary, summary end def test_complete_item item = TaskList::Item.new("[x]", "complete") assert item.complete?, "expected to be complete" end def test_incomplete_item item = TaskList::Item.new("[ ]", "incomplete") assert !item.complete?, "expected to be incomplete" end protected def task_list(text) TaskList.new(Record.new(text)) end end deckar01-task_list-2.0.0/test/units.css 0000644 0000041 0000041 00000000041 13155253103 017752 0 ustar www-data www-data /*= require qunit/qunit/qunit */ deckar01-task_list-2.0.0/test/task_list/ 0000755 0000041 0000041 00000000000 13155253103 020100 5 ustar www-data www-data deckar01-task_list-2.0.0/test/task_list/summary_test.rb 0000644 0000041 0000041 00000001641 13155253103 023163 0 ustar www-data www-data # encoding: utf-8 require File.expand_path('../../test_helper', __FILE__) require 'task_list/summary' class TaskList::SummaryTest < Minitest::Test def setup @complete = make_item "[x]", "complete" @incomplete = make_item "[ ]", "incomplete" @items = [@complete, @incomplete] @summary = make_summary @items end def test_no_items summary = make_summary [] assert !summary.items?, "no task list items are expected" end def test_items assert @summary.items?, "task list items are expected" assert_equal 2, @summary.item_count end def test_complete_count assert_equal 1, @summary.complete_count end def test_incomplete_count assert_equal 1, @summary.incomplete_count end protected def make_item(checkbox_text = "[ ]", source = "an item!") TaskList::Item.new(checkbox_text, source) end def make_summary(items) TaskList::Summary.new(items) end end deckar01-task_list-2.0.0/test/task_list/filter_test.rb 0000644 0000041 0000041 00000006657 13155253103 022767 0 ustar www-data www-data # encoding: utf-8 require File.expand_path('../../test_helper', __FILE__) require 'task_list/filter' class TaskList::FilterTest < Minitest::Test def setup @pipeline = HTML::Pipeline.new [ HTML::Pipeline::MarkdownFilter, TaskList::Filter ], {}, {} @context = {} @item_selector = "input.task-list-item-checkbox[type=checkbox]" end def test_has_no_effect_on_lists_with_no_tasks text = <<-md - plain - bullets md assert_equal 0, filter(text)[:output].css('ul.task-list').size end def test_filters_items_in_a_list text = <<-md - [ ] incomplete - [x] complete md assert_equal 2, filter(text)[:output].css(@item_selector).size end def test_filters_items_with_HTML_contents text = <<-md - [ ] incomplete **with bold** text - [x] complete __with italic__ text md assert_equal 2, filter(text)[:output].css(@item_selector).size end def test_filters_items_in_a_list_wrapped_in_paras # See issue #7951 for details. text = <<-md - [ ] one - [ ] this one will be wrapped in a para - [ ] this one too, wtf md assert_equal 3, filter(text)[:output].css(@item_selector).size end def test_populates_result_with_task_list_items text = <<-md - [ ] incomplete - [x] complete md result = filter(text) assert !result[:task_list_items].empty? incomplete, complete = result[:task_list_items] assert incomplete assert !incomplete.complete? assert complete assert complete.complete? end def test_skips_lists_in_code_blocks code = <<-md ``` - [ ] incomplete - [x] complete ``` md assert filter(code)[:output].css(@item_selector).empty?, "should not have any task list items" end def test_handles_encoding_correctly unicode = "中文" text = <<-md - [ ] #{unicode} md assert item = filter(text)[:output].css('.task-list-item').pop assert_equal unicode, item.text.strip end def test_handles_nested_items text = <<-md - [ ] one - [ ] one.one md assert item = filter(text)[:output].css('.task-list-item .task-list-item').pop end def test_handles_complicated_nested_items text = <<-md - [ ] one - [ ] one.one - [x] one.two - [ ] one.two.one - [ ] one.two.two - [ ] one.three - [ ] one.four - [ ] two - [x] two.one - [ ] two.two - [ ] three md assert_equal 6 + 2, filter(text)[:output].css('.task-list-item .task-list-item').size assert_equal 2, filter(text)[:output].css('.task-list-item .task-list-item .task-list-item').size end # NOTE: This is an edge case experienced regularly by users using a Swiss # German keyboard. # See: https://github.com/github/github/pull/18362 def test_non_breaking_space_between_brackets text = "- [\xC2\xA0] ok" assert item = filter(text)[:output].css('.task-list-item').pop, "item expected" assert_equal 'ok', item.text.strip end # See: https://github.com/github/github/pull/18362 def test_non_breaking_space_between_brackets_in_paras text = <<-md - [\xC2\xA0] one - [\xC2\xA0] this one will be wrapped in a para - [\xC2\xA0] this one too, wtf md assert_equal 3, filter(text)[:output].css(@item_selector).size end def test_capital_X text = <<-md - [x] lower case - [X] capital md assert_equal 2, filter(text)[:output].css("[checked]").size end protected def filter(input, context = @context, result = nil) result ||= {} @pipeline.call(input, context, result) end end deckar01-task_list-2.0.0/test/test_helper.rb 0000644 0000041 0000041 00000000100 13155253103 020735 0 ustar www-data www-data $:.unshift "lib" require 'minitest/autorun' require 'task_list' deckar01-task_list-2.0.0/test/unit/ 0000755 0000041 0000041 00000000000 13155253103 017062 5 ustar www-data www-data deckar01-task_list-2.0.0/test/unit/test_events.coffee 0000644 0000041 0000041 00000003755 13155253103 022610 0 ustar www-data www-data #= require jquery #= require task_list module "TaskList events", setup: -> @container = $ '