deckar01-task_list 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ // Requires use of `.markdown-body` to override default markdown list styles
2
+ .markdown-body .task-list {
3
+ list-style-type: none;
4
+ padding-left: 10px;
5
+ }
6
+
7
+ .task-list-item {
8
+ padding-left: 20px;
9
+ }
10
+ .task-list-item label {
11
+ font-weight: normal;
12
+ }
13
+ .task-list-item + .task-list-item {
14
+ margin-top: 3px;
15
+ }
16
+ .task-list-item-checkbox {
17
+ float: left;
18
+ margin-left: -20px;
19
+ margin-top: 4px;
20
+ }
data/bower.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "deckar01-task_list",
3
+ "version": "1.0.3",
4
+ "description": "Markdown TaskList components",
5
+ "homepage": "https://github.com/deckar01/task_list",
6
+ "dependencies": {
7
+ "jquery": ">= 1.9.1"
8
+ },
9
+ "devDependencies": {
10
+ "qunit": "^1.0.0"
11
+ },
12
+ "main": [
13
+ "app/assets/javascripts/task_list.coffee",
14
+ "app/assets/stylesheets/task_list.scss"
15
+ ],
16
+ "ignore": [
17
+ ".gitignore",
18
+ ".travis.yml",
19
+ "*.gemspec",
20
+ "*.md",
21
+ "config.ru",
22
+ "Gemfile",
23
+ "lib/",
24
+ "Rakefile",
25
+ "script/",
26
+ "test/"
27
+ ]
28
+ }
data/config.ru ADDED
@@ -0,0 +1,30 @@
1
+ # Rack environment for testing purposes
2
+
3
+ require 'coffee-script'
4
+ require 'json'
5
+ require 'sprockets'
6
+
7
+ Root = File.expand_path("..", __FILE__)
8
+
9
+ Assets = Sprockets::Environment.new(Root) do |env|
10
+ env.append_path "bower_components"
11
+ env.append_path "app/assets/javascripts"
12
+ env.append_path "app/assets/stylesheets"
13
+ env.append_path "test"
14
+ end
15
+
16
+ map "/assets" do
17
+ run Assets
18
+ end
19
+
20
+ map "/update" do
21
+ run lambda { |env|
22
+ sleep 0.5
23
+ req = Rack::Request.new(env)
24
+ [200, {'Content-Type' => 'application/json'}, [req.params.to_json]]
25
+ }
26
+ end
27
+
28
+ map "/" do
29
+ run Rack::Directory.new(Root)
30
+ end
data/lib/task_list.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'task_list/summary'
2
+ require 'task_list/version'
3
+
4
+ # encoding: utf-8
5
+ class TaskList
6
+ attr_reader :record
7
+
8
+ # `record` is the resource with the Markdown source text with task list items
9
+ # following this syntax:
10
+ #
11
+ # - [ ] a task list item
12
+ # - [ ] another item
13
+ # - [x] a completed item
14
+ #
15
+ def initialize(record)
16
+ @record = record
17
+ end
18
+
19
+ # Public: return the TaskList::Summary for this task list.
20
+ #
21
+ # Returns a TaskList::Summary.
22
+ def summary
23
+ @summary ||= TaskList::Summary.new(record.task_list_items)
24
+ end
25
+
26
+ class Item < Struct.new(:checkbox_text, :source)
27
+ Complete = /\[[xX]\]/.freeze # see TaskList::Filter
28
+
29
+ # Public: Check if a task list is complete.
30
+ #
31
+ # Examples
32
+ #
33
+ # Item.new(checkbox_text: "- [x]").complete?
34
+ # # => true
35
+ #
36
+ # Item.new(checkbox_text: "- [ ]").complete?
37
+ # # => false
38
+ #
39
+ # Returns true for checked list, false otherwise
40
+ def complete?
41
+ !!(checkbox_text =~ Complete)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,149 @@
1
+ # encoding: utf-8
2
+ require 'html/pipeline'
3
+ require 'task_list'
4
+
5
+ class TaskList
6
+ # Returns a `Nokogiri::DocumentFragment` object.
7
+ def self.filter(*args)
8
+ Filter.call(*args)
9
+ end
10
+
11
+ # TaskList filter replaces task list item markers (`[ ]` and `[x]`) with
12
+ # checkboxes, marked up with metadata and behavior.
13
+ #
14
+ # This should be run on the HTML generated by the Markdown filter, after the
15
+ # SanitizationFilter.
16
+ #
17
+ # Syntax
18
+ # ------
19
+ #
20
+ # Task list items must be in a list format:
21
+ #
22
+ # ```
23
+ # - [ ] incomplete
24
+ # - [x] complete
25
+ # ```
26
+ #
27
+ # Results
28
+ # -------
29
+ #
30
+ # The following keys are written to the result hash:
31
+ # :task_list_items - An array of TaskList::Item objects.
32
+ class Filter < HTML::Pipeline::Filter
33
+
34
+ Incomplete = "[ ]".freeze
35
+ Complete = "[x]".freeze
36
+
37
+ IncompletePattern = /\[[[:space:]]\]/.freeze # matches all whitespace
38
+ CompletePattern = /\[[xX]\]/.freeze # matches any capitalization
39
+
40
+ # Pattern used to identify all task list items.
41
+ # Useful when you need iterate over all items.
42
+ ItemPattern = /
43
+ ^
44
+ (?:\s*[-+*]|(?:\d+\.))? # optional list prefix
45
+ \s* # optional whitespace prefix
46
+ ( # checkbox
47
+ #{CompletePattern}|
48
+ #{IncompletePattern}
49
+ )
50
+ (?=\s) # followed by whitespace
51
+ /x
52
+
53
+ ListItemSelector = ".//li[task_list_item(.)]".freeze
54
+
55
+ class XPathSelectorFunction
56
+ def self.task_list_item(nodes)
57
+ nodes if nodes.text =~ ItemPattern
58
+ end
59
+ end
60
+
61
+ # Selects first P tag of an LI, if present
62
+ ItemParaSelector = "./p[1]".freeze
63
+
64
+ # List of `TaskList::Item` objects that were recognized in the document.
65
+ # This is available in the result hash as `:task_list_items`.
66
+ #
67
+ # Returns an Array of TaskList::Item objects.
68
+ def task_list_items
69
+ result[:task_list_items] ||= []
70
+ end
71
+
72
+ # Renders the item checkbox in a span including the item state.
73
+ #
74
+ # Returns an HTML-safe String.
75
+ def render_item_checkbox(item)
76
+ %(<input type="checkbox"
77
+ class="task-list-item-checkbox"
78
+ #{'checked="checked"' if item.complete?}
79
+ disabled="disabled"
80
+ />)
81
+ end
82
+
83
+ # Public: Marks up the task list item checkbox with metadata and behavior.
84
+ #
85
+ # NOTE: produces a string that, when assigned to a Node's `inner_html`,
86
+ # will corrupt the string contents' encodings. Instead, we parse the
87
+ # rendered HTML and explicitly set its encoding so that assignment will
88
+ # not change the encodings.
89
+ #
90
+ # See [this pull](https://github.com/github/github/pull/8505) for details.
91
+ #
92
+ # Returns the marked up task list item Nokogiri::XML::NodeSet object.
93
+ def render_task_list_item(item)
94
+ Nokogiri::HTML.fragment \
95
+ item.source.sub(ItemPattern, render_item_checkbox(item)), 'utf-8'
96
+ end
97
+
98
+ # Public: Select all task lists from the `doc`.
99
+ #
100
+ # Returns an Array of Nokogiri::XML::Element objects for ordered and
101
+ # unordered lists.
102
+ def list_items
103
+ doc.xpath(ListItemSelector, XPathSelectorFunction)
104
+ end
105
+
106
+ # Filters the source for task list items.
107
+ #
108
+ # Each item is wrapped in HTML to identify, style, and layer
109
+ # useful behavior on top of.
110
+ #
111
+ # Modifications apply to the parsed document directly.
112
+ #
113
+ # Returns nothing.
114
+ def filter!
115
+ list_items.reverse.each do |li|
116
+ add_css_class(li.parent, 'task-list')
117
+
118
+ outer, inner =
119
+ if p = li.xpath(ItemParaSelector)[0]
120
+ [p, p.inner_html]
121
+ else
122
+ [li, li.inner_html]
123
+ end
124
+ if match = (inner.chomp =~ ItemPattern && $1)
125
+ item = TaskList::Item.new(match, inner)
126
+ # prepend because we're iterating in reverse
127
+ task_list_items.unshift item
128
+
129
+ add_css_class(li, 'task-list-item')
130
+ outer.inner_html = render_task_list_item(item)
131
+ end
132
+ end
133
+ end
134
+
135
+ def call
136
+ filter!
137
+ doc
138
+ end
139
+
140
+ # Private: adds a CSS class name to a node, respecting existing class
141
+ # names.
142
+ def add_css_class(node, *new_class_names)
143
+ class_names = (node['class'] || '').split(' ')
144
+ return if new_class_names.all? { |klass| class_names.include?(klass) }
145
+ class_names.concat(new_class_names)
146
+ node['class'] = class_names.uniq.join(' ')
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,20 @@
1
+ class TaskList
2
+
3
+ def self.root_path
4
+ @root_path ||= Pathname.new(File.expand_path("../../../", __FILE__))
5
+ end
6
+
7
+ def self.asset_paths
8
+ @paths ||= Dir[root_path.join("app/assets/*")]
9
+ end
10
+
11
+ if defined? ::Rails::Railtie
12
+ class Railtie < ::Rails::Railtie
13
+ initializer "task_list" do |app|
14
+ TaskList.asset_paths.each do |path|
15
+ app.config.assets.paths << path
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ require 'html/pipeline'
3
+ require 'task_list'
4
+
5
+ class TaskList
6
+ # Provides a summary of provided TaskList `items`.
7
+ #
8
+ # `items` is an Array of TaskList::Item objects.
9
+ class Summary < Struct.new(:items)
10
+ # Public: returns true if there are any TaskList::Item objects.
11
+ def items?
12
+ item_count > 0
13
+ end
14
+
15
+ # Public: returns the number of TaskList::Item objects.
16
+ def item_count
17
+ items.size
18
+ end
19
+
20
+ # Public: returns the number of complete TaskList::Item objects.
21
+ def complete_count
22
+ items.select{ |i| i.complete? }.size
23
+ end
24
+
25
+ # Public: returns the number of incomplete TaskList::Item objects.
26
+ def incomplete_count
27
+ items.select{ |i| !i.complete? }.size
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ class TaskList
2
+ VERSION = [1, 0, 3].join('.')
3
+ end
data/script/bootstrap ADDED
@@ -0,0 +1,13 @@
1
+ #!/bin/sh
2
+ set -e 0
3
+
4
+ if ! bundle check 1>/dev/null 2>&1; then
5
+ bundle install --no-color --binstubs --path vendor/gems
6
+ fi
7
+
8
+ if ! npm list bower 2>&1 | grep 0.8.5 >/dev/null; then
9
+ # npm install bower
10
+ npm install git://github.com/twitter/bower.git
11
+ fi
12
+
13
+ bower install --no-color
data/script/cibuild ADDED
@@ -0,0 +1,6 @@
1
+ #!/bin/sh -e
2
+ # Usage: script/cibuild
3
+ # CI build script.
4
+
5
+ ./script/testsuite 4018
6
+ bundle exec rake test
data/script/testsuite ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ root = File.expand_path("../..", __FILE__)
4
+ Dir.chdir root
5
+
6
+ port = ARGV[0] || 4000
7
+
8
+ pid = fork do
9
+ $stderr.reopen "/dev/null" # silence WEBrick output
10
+ exec 'bundle', 'exec', 'rackup', '-p', port.to_s
11
+ end
12
+ sleep 1
13
+
14
+ status = system('phantomjs', "#{root}/test/run-qunit.coffee", "http://localhost:#{port}/test/index.html")
15
+
16
+ Process.kill 'SIGINT', pid
17
+ Process.wait pid
18
+
19
+ exit status
data/task_list.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'task_list/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "deckar01-task_list"
8
+ gem.version = TaskList::VERSION
9
+ gem.authors = ["Jared Deckard", "Matt Todd"]
10
+ gem.email = ["jared.deckard@gmail.com"]
11
+ gem.description = %q{Markdown TaskList components}
12
+ gem.summary = %q{Markdown TaskList components}
13
+
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_dependency "rack", "1.6.4"
20
+ gem.add_dependency "activesupport", "4.2.6"
21
+ gem.add_dependency "html-pipeline"
22
+
23
+ gem.add_development_dependency "github-markdown"
24
+ gem.add_development_dependency "rake"
25
+ gem.add_development_dependency "coffee-script"
26
+ gem.add_development_dependency "json"
27
+ gem.add_development_dependency "rack"
28
+ gem.add_development_dependency "sprockets"
29
+ gem.add_development_dependency "minitest", "~> 5.3.2"
30
+ end
@@ -0,0 +1,3 @@
1
+ #= require jquery
2
+ #= require rails-behaviors/remote
3
+ #= require task_list
@@ -0,0 +1,103 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <script type="text/javascript" src="/assets/functional/helpers/remote.js"></script>
6
+ <script type="text/javascript">
7
+ function logEvent(event) {
8
+ console.log(event)
9
+ $('.log').prepend("<li>" + event.type + "</li>")
10
+ }
11
+
12
+ setInterval(function() {
13
+ if (!$('.log li:first').first().hasClass("timestamp"))
14
+ $('.log').prepend("<li class='timestamp'><time>" + new Date + "</time></li>")
15
+ }, 3000)
16
+
17
+ $(document).on('tasklist:enabled', logEvent)
18
+ $(document).on('tasklist:disabled', logEvent)
19
+ $(document).on('tasklist:change', '.js-task-list-field', function(event){
20
+ logEvent(event)
21
+ $(this).closest('.js-task-list-container').taskList('disable')
22
+ })
23
+ $(document).on('tasklist:changed', '.js-task-list-field', function(event){
24
+ logEvent(event)
25
+ $(this).closest('form').submit()
26
+ })
27
+
28
+ $(document).on('ajaxStart', logEvent)
29
+ $(document).on('ajaxSuccess', '.js-task-list-container', function(event){
30
+ logEvent(event)
31
+ $(this).taskList()
32
+ })
33
+ </script>
34
+
35
+ <style>
36
+ body {
37
+ font: 13px Helvetica;
38
+ padding: 20px;
39
+ }
40
+ input {
41
+ font-size: 20px;
42
+ }
43
+ time {
44
+ font-size: 10px;
45
+ font-style: oblique;
46
+ }
47
+
48
+ .task-list {
49
+ list-style-type: none;
50
+ }
51
+ .task-list .task-list-item {
52
+ opacity: 0.75;
53
+ }
54
+ .task-list .task-list-item.enabled {
55
+ opacity: 1.0;
56
+ }
57
+
58
+ .log {
59
+ position: absolute;
60
+ top: 20px;
61
+ right: 50px;
62
+ color: #222;
63
+ list-style: none;
64
+ margin: 0;
65
+ padding: 0;
66
+ }
67
+ </style>
68
+ </head>
69
+ <body>
70
+ <div class="js-task-list-container js-task-list-enable">
71
+ <div class="markdown">
72
+ <ul class="task-list">
73
+ <li class="task-list-item">
74
+ <input type="checkbox" class="task-list-item-checkbox" disabled />
75
+ I'm a task list item
76
+ </li>
77
+ <li class="task-list-item">
78
+ <input type="checkbox" class="task-list-item-checkbox" disabled />
79
+ with non-breaking space
80
+ </li>
81
+ <li class="task-list-item">
82
+ <input type="checkbox" class="task-list-item-checkbox" disabled checked />
83
+ completed, lower
84
+ </li>
85
+ <li class="task-list-item">
86
+ <input type="checkbox" class="task-list-item-checkbox" disabled checked />
87
+ completed capitalized
88
+ </li>
89
+ </ul>
90
+ </div>
91
+ <form action="/update" method="POST" data-remote data-type="json">
92
+ <textarea name="comment[body]" class="js-task-list-field" cols="40" rows="10">
93
+ - [ ] I'm a task list item
94
+ - [ ] with non-breaking space
95
+ - [x] completed, lower
96
+ - [X] completed capitalized</textarea>
97
+ </form>
98
+ </div>
99
+
100
+ <ul class="log">
101
+ </ul>
102
+ </body>
103
+ </html>