deckar01-task_list-2.0.0/0000755000004100000410000000000013155253103015124 5ustar www-datawww-datadeckar01-task_list-2.0.0/Rakefile0000644000004100000410000000027613155253103016576 0ustar www-datawww-datarequire "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.gemspec0000644000004100000410000000251113155253103020465 0ustar www-datawww-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/Gemfile0000644000004100000410000000004613155253103016417 0ustar www-datawww-datasource "https://rubygems.org" gemspec deckar01-task_list-2.0.0/script/0000755000004100000410000000000013155253103016430 5ustar www-datawww-datadeckar01-task_list-2.0.0/script/testsuite0000755000004100000410000000046213155253103020411 0ustar www-datawww-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/bootstrap0000755000004100000410000000024013155253103020367 0ustar www-datawww-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/cibuild0000755000004100000410000000016413155253103017772 0ustar www-datawww-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.yml0000644000004100000410000000060213155253103017233 0ustar www-datawww-datalanguage: 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/0000755000004100000410000000000013155253103015672 5ustar www-datawww-datadeckar01-task_list-2.0.0/lib/task_list.rb0000644000004100000410000000175513155253103020224 0ustar www-datawww-datarequire '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/0000755000004100000410000000000013155253103017667 5ustar www-datawww-datadeckar01-task_list-2.0.0/lib/task_list/filter.rb0000644000004100000410000001073513155253103021507 0ustar www-datawww-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.rb0000644000004100000410000000006313155253103021700 0ustar www-datawww-dataclass TaskList VERSION = [2, 0, 0].join('.') end deckar01-task_list-2.0.0/lib/task_list/railtie.rb0000644000004100000410000000065713155253103021655 0ustar www-datawww-dataclass 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.rb0000644000004100000410000000134713155253103021716 0ustar www-datawww-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/0000755000004100000410000000000013155253103016103 5ustar www-datawww-datadeckar01-task_list-2.0.0/test/task_list_test.rb0000644000004100000410000000134513155253103021467 0ustar www-datawww-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.css0000644000004100000410000000004113155253103017752 0ustar www-datawww-data/*= require qunit/qunit/qunit */ deckar01-task_list-2.0.0/test/task_list/0000755000004100000410000000000013155253103020100 5ustar www-datawww-datadeckar01-task_list-2.0.0/test/task_list/summary_test.rb0000644000004100000410000000164113155253103023163 0ustar www-datawww-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.rb0000644000004100000410000000665713155253103022767 0ustar www-datawww-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.rb0000644000004100000410000000010013155253103020735 0ustar www-datawww-data$:.unshift "lib" require 'minitest/autorun' require 'task_list' deckar01-task_list-2.0.0/test/unit/0000755000004100000410000000000013155253103017062 5ustar www-datawww-datadeckar01-task_list-2.0.0/test/unit/test_events.coffee0000644000004100000410000000375513155253103022610 0ustar www-datawww-data#= require jquery #= require task_list module "TaskList events", setup: -> @container = $ '
', class: 'js-task-list-container' @list = $ '
deckar01-task_list-2.0.0/test/functional/helpers/0000755000004100000410000000000013155253103021707 5ustar www-datawww-datadeckar01-task_list-2.0.0/test/functional/helpers/remote.coffee0000644000004100000410000000011113155253103024344 0ustar www-datawww-data#= require jquery #= require rails-behaviors/remote #= require task_list deckar01-task_list-2.0.0/dist/0000755000004100000410000000000013155253103016067 5ustar www-datawww-datadeckar01-task_list-2.0.0/dist/.npmignore0000644000004100000410000000000013155253103020054 0ustar www-datawww-datadeckar01-task_list-2.0.0/dist/.gitignore0000644000004100000410000000001313155253103020051 0ustar www-datawww-data*.css *.js deckar01-task_list-2.0.0/package.json0000644000004100000410000000230713155253103017414 0ustar www-datawww-data{ "name": "deckar01-task_list", "version": "2.0.0", "description": "Markdown TaskList components", "main": "dist/task_list.js", "directories": { "test": "test" }, "files": [ "dist" ], "scripts": { "test": "script/cibuild", "qunit": "phantomjs test/run-qunit.coffee http://localhost:4018/test/index.html", "lint": "coffeelint app/assets/javascripts/task_list.coffee", "build:css": "node-sass -o dist/ app/assets/stylesheets/task_list.scss", "build:js": "webpack", "build": "npm run build:css && npm run build:js", "prepublish": "npm run build" }, "repository": { "type": "git", "url": "git+https://github.com/deckar01/task_list.git" }, "keywords": [ "task", "list", "markdown", "ruby", "check" ], "author": "Jared Deckard ", "license": "MIT", "bugs": { "url": "https://github.com/deckar01/task_list/issues" }, "homepage": "https://github.com/deckar01/task_list#readme", "devDependencies": { "bower": "^1.8.0", "coffee-loader": "^0.7.3", "coffee-script": "^1.12.5", "coffeelint": "^1.16.0", "node-sass": "^4.5.2", "phantomjs": "^1.9.19", "webpack": "^2.5.1" } } deckar01-task_list-2.0.0/.gitignore0000644000004100000410000000031213155253103017110 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc bin/ coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp bower_components node_modules vendor/gems/ deckar01-task_list-2.0.0/LICENSE0000644000004100000410000000213013155253103016125 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2016 Jared Deckard Copyright (c) 2014 GitHub, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. deckar01-task_list-2.0.0/webpack.config.js0000644000004100000410000000052713155253103020346 0ustar www-datawww-datamodule.exports = { entry: './app/assets/javascripts/task_list.coffee', output: { filename: 'dist/task_list.js', libraryTarget: 'umd', library: 'TaskList', }, module: { loaders: [ { test: /\.coffee$/, loader: 'coffee-loader' } ] }, resolve: { extensions: ['.coffee', '.js'] } } deckar01-task_list-2.0.0/bower.json0000644000004100000410000000104613155253103017136 0ustar www-datawww-data{ "name": "deckar01-task_list", "version": "2.0.0", "description": "Markdown TaskList components", "homepage": "https://github.com/deckar01/task_list", "devDependencies": { "jquery": ">= 1.9.1", "qunit": "^1.0.0", "rails-behaviors": "^0.8.4" }, "main": [ "app/assets/javascripts/task_list.coffee", "app/assets/stylesheets/task_list.scss" ], "ignore": [ ".gitignore", ".travis.yml", "*.gemspec", "*.md", "config.ru", "Gemfile", "lib/", "Rakefile", "script/", "test/" ] } deckar01-task_list-2.0.0/app/0000755000004100000410000000000013155253103015704 5ustar www-datawww-datadeckar01-task_list-2.0.0/app/assets/0000755000004100000410000000000013155253103017206 5ustar www-datawww-datadeckar01-task_list-2.0.0/app/assets/javascripts/0000755000004100000410000000000013155253103021537 5ustar www-datawww-datadeckar01-task_list-2.0.0/app/assets/javascripts/task_list.coffee0000644000004100000410000001711413155253103024711 0ustar www-datawww-data# TaskList Behavior # #= provides TaskList # # Enables Task List update behavior. # # ### Example Markup # #
# #
# #
#
# # ### Specification # # TaskLists MUST be contained in a `(div).js-task-list-container`. # # TaskList Items SHOULD be an a list (`UL`/`OL`) element. # # Task list items MUST match `(input).task-list-item-checkbox` and MUST be # `disabled` by default. # # TaskLists MUST have a `(textarea).js-task-list-field` form element whose # `value` attribute is the source (Markdown) to be udpated. The source MUST # follow the syntax guidelines. # # TaskList updates trigger `tasklist:change` events. If the change is # successful, `tasklist:changed` is fired. The change can be canceled. # # ### Methods # # `.taskList('enable')` or `.taskList()` # # Enables TaskList updates for the container. # # `.taskList('disable')` # # Disables TaskList updates for the container. # ## ### Events # # `tasklist:enabled` # # Fired when the TaskList is enabled. # # * **Synchronicity** Sync # * **Bubbles** Yes # * **Cancelable** No # * **Target** `.js-task-list-container` # # `tasklist:disabled` # # Fired when the TaskList is disabled. # # * **Synchronicity** Sync # * **Bubbles** Yes # * **Cancelable** No # * **Target** `.js-task-list-container` # # `tasklist:change` # # Fired before the TaskList item change takes affect. # # * **Synchronicity** Sync # * **Bubbles** Yes # * **Cancelable** Yes # * **Target** `.js-task-list-field` # # `tasklist:changed` # # Fired once the TaskList item change has taken affect. # # * **Synchronicity** Sync # * **Bubbles** Yes # * **Cancelable** No # * **Target** `.js-task-list-field` # # ### NOTE # # Task list checkboxes are rendered as disabled by default because rendered # user content is cached without regard for the viewer. NodeArray = (nodeList) -> Array.prototype.slice.apply(nodeList) closest = (el, className) -> while el && !el.classList.contains className el = el.parentNode el createEvent = (eventName, detail) -> if typeof Event == 'function' event = new Event eventName, {bubbles: true, cancelable: true} event.detail = detail else event = document.createEvent 'CustomEvent' event.initCustomEvent eventName, true, true, detail event class TaskList constructor: (@el) -> @container = closest @el, 'js-task-list-container' @field = @container.querySelector '.js-task-list-field' # When the task list item checkbox is updated, submit the change @container.addEventListener 'change', (event) => if event.target.classList.contains 'task-list-item-checkbox' @updateTaskList(event.target) @.enable() enable: -> if @container.querySelectorAll('.js-task-list-field').length > 0 NodeArray(@container.querySelectorAll('.task-list-item')). forEach (item) -> item.classList.add('enabled') NodeArray(@container.querySelectorAll('.task-list-item-checkbox')). forEach (checkbox) -> checkbox.disabled = false @container.classList.add 'is-task-list-enabled' event = createEvent 'tasklist:enabled' @container.dispatchEvent event disable: -> NodeArray(@container.querySelectorAll('.task-list-item')). forEach (item) -> item.classList.remove('enabled') NodeArray(@container.querySelectorAll('.task-list-item-checkbox')). forEach (checkbox) -> checkbox.disabled = true @container.classList.remove('is-task-list-enabled') event = createEvent 'tasklist:disabled' @container.dispatchEvent event # Updates the field value to reflect the state of item. # Triggers the `tasklist:change` event before the value has changed, and fires # a `tasklist:changed` event once the value has changed. updateTaskList: (item) -> checkboxes = @container.querySelectorAll('.task-list-item-checkbox') index = 1 + NodeArray(checkboxes).indexOf item changeEvent = createEvent 'tasklist:change', index: index checked: item.checked @field.dispatchEvent changeEvent unless changeEvent.defaultPrevented @field.value = TaskList.updateSource(@field.value, index, item.checked) changeEvent = createEvent 'change' @field.dispatchEvent changeEvent changedEvent = createEvent 'tasklist:changed', index: index checked: item.checked @field.dispatchEvent changedEvent # Static interface @incomplete: "[ ]" @complete: "[x]" # Escapes the String for regular expression matching. @escapePattern: (str) -> str. replace(/([\[\]])/g, "\\$1"). # escape square brackets replace(/\s/, "\\s"). # match all white space replace("x", "[xX]") # match all cases @incompletePattern: /// #{@escapePattern(@incomplete)} /// @completePattern: /// #{@escapePattern(@complete)} /// # Pattern used to identify all task list items. # Useful when you need iterate over all items. @itemPattern: /// ^ (?: # prefix, consisting of \s* # optional leading whitespace (?:>\s*)* # zero or more blockquotes (?:[-+*]|(?:\d+\.)) # list item indicator ) \s* # optional whitespace prefix ( # checkbox #{@escapePattern(@complete)}| #{@escapePattern(@incomplete)} ) \s+ # is followed by whitespace (?! \(.*?\) # is not part of a [foo](url) link ) (?= # and is followed by zero or more links (?:\[.*?\]\s*(?:\[.*?\]|\(.*?\))\s*)* (?:[^\[]|$) # and either a non-link or the end of the string ) /// # Used to skip checkbox markup inside of code fences. # http://rubular.com/r/TfCDNsy8x4 @startFencesPattern: /^`{3}.*$/ @endFencesPattern: /^`{3}$/ # Used to filter out potential mismatches (items not in lists). # http://rubular.com/r/OInl6CiePy @itemsInParasPattern: /// ^ ( #{@escapePattern(@complete)}| #{@escapePattern(@incomplete)} ) .+ $ ///g # Given the source text, updates the appropriate task list item to match the # given checked value. # # Returns the updated String text. @updateSource: (source, itemIndex, checked) -> clean = source.replace(/\r/g, ''). replace(@itemsInParasPattern, ''). split("\n") index = 0 inCodeBlock = false result = for line in source.split("\n") if inCodeBlock # Lines inside of a code block are ignored. if line.match(@endFencesPattern) # Stop ignoring lines once the code block is closed. inCodeBlock = false else if line.match(@startFencesPattern) # Start ignoring lines inside a code block. inCodeBlock = true else if line in clean && line.match(@itemPattern) index += 1 if index == itemIndex line = if checked line.replace(@incompletePattern, @complete) else line.replace(@completePattern, @incomplete) line result.join("\n") if typeof jQuery != 'undefined' jQuery.fn.taskList = (method) -> this.each (index, el) -> taskList = jQuery(el).data('task-list') if !taskList taskList = new TaskList el jQuery(el).data 'task-list', taskList if !method || method == 'enable' return taskList[method || 'enable']() module.exports = TaskList deckar01-task_list-2.0.0/app/assets/stylesheets/0000755000004100000410000000000013155253103021562 5ustar www-datawww-datadeckar01-task_list-2.0.0/app/assets/stylesheets/task_list.scss0000644000004100000410000000060413155253103024454 0ustar www-datawww-data// Requires use of `.markdown-body` to override default markdown list styles .markdown-body .task-list { list-style-type: none; padding-left: 10px; } .task-list-item { padding-left: 20px; } .task-list-item label { font-weight: normal; } .task-list-item + .task-list-item { margin-top: 3px; } .task-list-item-checkbox { float: left; margin-left: -20px; margin-top: 4px; } deckar01-task_list-2.0.0/README.md0000644000004100000410000001224313155253103016405 0ustar www-datawww-data# Task Lists [![Build Status](http://img.shields.io/travis/deckar01/task_list.svg)][travis] [travis]: https://travis-ci.org/deckar01/task_list This is a community fork of GitHub's archived [`task_list`][task_list] gem. [task_list]: https://github.com/github-archive/task_list ```md - [x] Get - [x] More - [ ] Done ``` > - [x] Get > - [x] More > - [ ] Done ## Components The Task List feature is made of several different components: * Markdown Ruby Filter * Summary Ruby Model: summarizes task list items * JavaScript: frontend task list update behavior * CSS: styles Markdown task list items ## Usage & Integration The backend components are designed for rendering the Task List item checkboxes, and the frontend components handle updating the Markdown source (embedded in the markup). ### Backend: Markdown pipeline filter Rendering Task List item checkboxes from source Markdown depends on the `TaskList::Filter`, designed to integrate with the [`html-pipeline`](https://github.com/jch/html-pipeline) gem. For example: ``` ruby require 'html/pipeline' require 'task_list/filter' pipeline = HTML::Pipeline.new [ HTML::Pipeline::MarkdownFilter, TaskList::Filter ] pipeline.call "- [ ] task list item" ``` ### Frontend: Markdown Updates Task List updates on the frontend require specific HTML markup structure, and must be enabled with JavaScript. Rendered HTML (the `