todidnt 0.1.0 → 0.2.0
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.
- data/.gitignore +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +8 -2
- data/lib/todidnt/html_generator.rb +64 -0
- data/lib/todidnt/todo_line.rb +12 -1
- data/lib/todidnt.rb +175 -8
- data/templates/all.erb +161 -0
- data/templates/css/chosen.min.css +3 -0
- data/templates/css/style.css +129 -0
- data/templates/history.erb +5 -0
- data/templates/js/JSXTransformer.js +12499 -0
- data/templates/js/chosen.jquery.min.js +2 -0
- data/templates/js/jquery-2.1.0.min.js +4 -0
- data/templates/js/react.js +16644 -0
- data/templates/js/react.min.js +21 -0
- data/templates/js/todidnt-history.js +117 -0
- data/templates/js/underscore.min.js +6 -0
- data/templates/layout.erb +26 -0
- data/todidnt.gemspec +2 -2
- metadata +17 -2
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,16 +1,22 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
+
addressable (2.3.5)
|
5
|
+
chronic (0.10.2)
|
6
|
+
launchy (2.4.2)
|
7
|
+
addressable (~> 2.3)
|
4
8
|
metaclass (0.0.1)
|
5
9
|
minitest (5.0.8)
|
6
10
|
mocha (0.14.0)
|
7
11
|
metaclass (~> 0.0.1)
|
8
|
-
|
12
|
+
tilt (2.0.0)
|
9
13
|
|
10
14
|
PLATFORMS
|
11
15
|
ruby
|
12
16
|
|
13
17
|
DEPENDENCIES
|
18
|
+
chronic
|
19
|
+
launchy
|
14
20
|
minitest
|
15
21
|
mocha
|
16
|
-
|
22
|
+
tilt
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
require 'erb'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Todidnt
|
7
|
+
class HTMLGenerator
|
8
|
+
SOURCE_PATH = File.join(
|
9
|
+
File.dirname(File.expand_path(__FILE__)),
|
10
|
+
'../../templates'
|
11
|
+
)
|
12
|
+
DESTINATION_PATH = '.todidnt'
|
13
|
+
|
14
|
+
def self.generate_common
|
15
|
+
# Create the destination folder unless it already exists.
|
16
|
+
Dir.mkdir(DESTINATION_PATH) unless Dir.exists?(DESTINATION_PATH)
|
17
|
+
|
18
|
+
# Copy over directories (e.g. js, css) to the destination.
|
19
|
+
common_dirs = []
|
20
|
+
Dir.chdir(SOURCE_PATH) do
|
21
|
+
common_dirs = Dir.glob('*').select do |dir|
|
22
|
+
File.directory?(dir)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
common_dirs.each do |dir|
|
27
|
+
FileUtils.cp_r(
|
28
|
+
source_path(dir),
|
29
|
+
destination_path,
|
30
|
+
:remove_destination => true
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.generate(template, context={})
|
36
|
+
generate_common
|
37
|
+
|
38
|
+
content_template = from_template(template)
|
39
|
+
layout_template = from_template(:layout)
|
40
|
+
|
41
|
+
inner_content = content_template.render nil, context
|
42
|
+
result = layout_template.render { inner_content }
|
43
|
+
|
44
|
+
file_name = destination_path("todidnt_#{template}.html")
|
45
|
+
File.open(file_name, 'w') do |file|
|
46
|
+
file.write(result)
|
47
|
+
end
|
48
|
+
|
49
|
+
File.absolute_path(file_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.source_path(path=nil)
|
53
|
+
path ? "#{SOURCE_PATH}/#{path}" : SOURCE_PATH
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.destination_path(path=nil)
|
57
|
+
path ? "#{DESTINATION_PATH}/#{path}" : DESTINATION_PATH
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.from_template(template)
|
61
|
+
Tilt::ERBTemplate.new(source_path("#{template}.erb"))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/todidnt/todo_line.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Todidnt
|
2
2
|
class TodoLine
|
3
|
-
IGNORE = %r{assets/js|third_?party|node_modules|jquery|Binary}
|
3
|
+
IGNORE = %r{assets/js|third_?party|node_modules|jquery|Binary|vendor}
|
4
4
|
|
5
5
|
attr_reader :filename, :line_number, :content, :author, :timestamp
|
6
6
|
|
@@ -28,6 +28,7 @@ module Todidnt
|
|
28
28
|
options = [
|
29
29
|
['--line-porcelain'],
|
30
30
|
['-L', "#{@line_number},#{@line_number}"],
|
31
|
+
['-w'],
|
31
32
|
[@filename]
|
32
33
|
]
|
33
34
|
|
@@ -48,5 +49,15 @@ module Todidnt
|
|
48
49
|
def pretty
|
49
50
|
"#{pretty_time} (#{author}, #{filename}:#{line_number}): #{content}"
|
50
51
|
end
|
52
|
+
|
53
|
+
def to_hash
|
54
|
+
{
|
55
|
+
:time => pretty_time,
|
56
|
+
:author => author,
|
57
|
+
:filename => filename,
|
58
|
+
:line_number => line_number,
|
59
|
+
:content => content
|
60
|
+
}
|
61
|
+
end
|
51
62
|
end
|
52
63
|
end
|
data/lib/todidnt.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
require 'todidnt/git_repo'
|
2
2
|
require 'todidnt/git_command'
|
3
3
|
require 'todidnt/todo_line'
|
4
|
+
require 'todidnt/html_generator'
|
4
5
|
|
5
6
|
require 'chronic'
|
7
|
+
require 'launchy'
|
6
8
|
|
7
9
|
module Todidnt
|
8
10
|
class CLI
|
9
|
-
VALID_COMMANDS = %w{all overdue}
|
11
|
+
VALID_COMMANDS = %w{all overdue history}
|
10
12
|
|
11
13
|
def self.run(command, options)
|
12
14
|
if command && VALID_COMMANDS.include?(command)
|
@@ -20,14 +22,12 @@ module Todidnt
|
|
20
22
|
end
|
21
23
|
|
22
24
|
def self.all(options)
|
23
|
-
all_lines = self.all_lines(options)
|
25
|
+
all_lines = self.all_lines(options).sort_by(&:timestamp)
|
24
26
|
|
25
|
-
puts "\
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
puts line.pretty
|
30
|
-
end
|
27
|
+
puts "\nOpening results..."
|
28
|
+
|
29
|
+
file_path = HTMLGenerator.generate(:all, :all_lines => all_lines)
|
30
|
+
Launchy.open("file://#{file_path}")
|
31
31
|
end
|
32
32
|
|
33
33
|
def self.overdue(options)
|
@@ -65,5 +65,172 @@ module Todidnt
|
|
65
65
|
lines
|
66
66
|
end
|
67
67
|
end
|
68
|
+
|
69
|
+
def self.history(options)
|
70
|
+
GitRepo.new(options[:path]).run do |path|
|
71
|
+
log = GitCommand.new(:log, [['-G', 'TODO'], ['--format="COMMIT %an %ae %at"'], ['-p'], ['-U0']])
|
72
|
+
|
73
|
+
history = []
|
74
|
+
|
75
|
+
blame_hash = {}
|
76
|
+
|
77
|
+
puts "Going through log..."
|
78
|
+
patch_additions = []
|
79
|
+
patch_deletions = []
|
80
|
+
filename = nil
|
81
|
+
total = log.output_lines.count
|
82
|
+
|
83
|
+
log.output_lines.reverse.each do |line|
|
84
|
+
if (summary = /^COMMIT (.*) (.*) (.*)/.match(line))
|
85
|
+
name = summary[1]
|
86
|
+
email = summary[2]
|
87
|
+
time = summary[3]
|
88
|
+
|
89
|
+
unless filename =~ TodoLine::IGNORE
|
90
|
+
# Put the additions in the blame hash so when someone removes we
|
91
|
+
# can tell who the original author was. Mrrrh, this isn't going to
|
92
|
+
# work if people add the same string (pretty common e.g. # TODO).
|
93
|
+
# We can figure this out later though.
|
94
|
+
patch_additions.each do |line|
|
95
|
+
blame_hash[line] ||= []
|
96
|
+
blame_hash[line] << name
|
97
|
+
end
|
98
|
+
|
99
|
+
deletions_by_author = {}
|
100
|
+
patch_deletions.each do |line|
|
101
|
+
author = blame_hash[line] && blame_hash[line].pop
|
102
|
+
|
103
|
+
if author
|
104
|
+
deletions_by_author[author] ||= 0
|
105
|
+
deletions_by_author[author] += 1
|
106
|
+
else
|
107
|
+
puts "BAD BAD can't find original author: #{line}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
history << {
|
112
|
+
:timestamp => time.to_i,
|
113
|
+
:author => name,
|
114
|
+
:additions => patch_additions.count,
|
115
|
+
:deletions => deletions_by_author[name] || 0
|
116
|
+
}
|
117
|
+
|
118
|
+
deletions_by_author.delete(name)
|
119
|
+
deletions_by_author.each do |author, deletion_count|
|
120
|
+
history << {
|
121
|
+
:timestamp => time.to_i,
|
122
|
+
:author => author,
|
123
|
+
:additions => 0,
|
124
|
+
:deletions => deletion_count
|
125
|
+
}
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
patch_additions = []
|
130
|
+
patch_deletions = []
|
131
|
+
elsif (diff = /diff --git a\/(.*) b\/(.*)/.match(line))
|
132
|
+
filename = diff[1]
|
133
|
+
elsif (diff = /^\+(.*TODO.*)/.match(line))
|
134
|
+
patch_additions << diff[1]
|
135
|
+
elsif (diff = /^\-(.*TODO.*)/.match(line))
|
136
|
+
patch_deletions << diff[1]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
history.sort_by! {|slice| slice[:timestamp]}
|
141
|
+
min_commit_date = Time.at(history.first[:timestamp])
|
142
|
+
max_commit_date = Time.at(history.last[:timestamp])
|
143
|
+
|
144
|
+
timespan = max_commit_date - min_commit_date
|
145
|
+
|
146
|
+
# Figure out what the interval should be based on the total timespan.
|
147
|
+
if timespan > 86400 * 365 * 10 # 10+ years
|
148
|
+
interval = 86400 * 365 # years
|
149
|
+
elsif timespan > 86400 * 365 * 5 # 5-10 years
|
150
|
+
interval = 86400 * (365 / 2) # 6 months
|
151
|
+
elsif timespan > 86400 * 365 # 2-5 years
|
152
|
+
interval = 86400 * (365 / 4) # 3 months
|
153
|
+
elsif timespan > 86400 * 30 * 6 # 6 months-3 year
|
154
|
+
interval = 86400 * 30 # months
|
155
|
+
elsif timespan > 86400 * 1 # 1 month - 6 months
|
156
|
+
interval = 86400 * 7
|
157
|
+
else # 0 - 2 months
|
158
|
+
interval = 86400 # days
|
159
|
+
end
|
160
|
+
|
161
|
+
original_interval_start = Time.new(min_commit_date.year, min_commit_date.month, min_commit_date.day).to_i
|
162
|
+
interval_start = original_interval_start
|
163
|
+
interval_end = interval_start + interval
|
164
|
+
|
165
|
+
puts "Finalizing timeline..."
|
166
|
+
buckets = []
|
167
|
+
current_bucket_authors = {}
|
168
|
+
bucket_total = 0
|
169
|
+
|
170
|
+
i = 0
|
171
|
+
# Going through the entire history of +/-'s of TODOs.
|
172
|
+
while i < history.length
|
173
|
+
should_increment = false
|
174
|
+
slice = history[i]
|
175
|
+
author = slice[:author]
|
176
|
+
|
177
|
+
# Does the current slice exist inside the bucket we're currently
|
178
|
+
# in? If so, add it to the author's total and go to the next slice.
|
179
|
+
if slice[:timestamp] >= interval_start && slice[:timestamp] < interval_end
|
180
|
+
current_bucket_authors[author] ||= 0
|
181
|
+
current_bucket_authors[author] += slice[:additions] - slice[:deletions]
|
182
|
+
bucket_total += slice[:additions] - slice[:deletions]
|
183
|
+
should_increment = true
|
184
|
+
end
|
185
|
+
|
186
|
+
# If we're on the last slice, or the next slice would have been
|
187
|
+
# in a new bucket, finish the current bucket.
|
188
|
+
if i == (history.length - 1) || history[i + 1][:timestamp] >= interval_end
|
189
|
+
buckets << {
|
190
|
+
:timestamp => Time.at(interval_start).strftime('%D'),
|
191
|
+
:authors => current_bucket_authors,
|
192
|
+
:total => bucket_total
|
193
|
+
}
|
194
|
+
interval_start += interval
|
195
|
+
interval_end += interval
|
196
|
+
|
197
|
+
current_bucket_authors = current_bucket_authors.clone
|
198
|
+
end
|
199
|
+
|
200
|
+
i += 1 if should_increment
|
201
|
+
end
|
202
|
+
|
203
|
+
authors = Set.new
|
204
|
+
contains_other = false
|
205
|
+
buckets.each do |bucket|
|
206
|
+
significant_authors = {}
|
207
|
+
other_count = 0
|
208
|
+
bucket[:authors].each do |author, count|
|
209
|
+
# Only include the author if they account for more than > 3% of
|
210
|
+
# the TODOs in this bucket.
|
211
|
+
if count > bucket[:total] * 0.03
|
212
|
+
significant_authors[author] = count
|
213
|
+
authors << author
|
214
|
+
else
|
215
|
+
other_count += count
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
if other_count > 0
|
220
|
+
significant_authors['Other'] = other_count
|
221
|
+
contains_other = true
|
222
|
+
end
|
223
|
+
|
224
|
+
bucket[:authors] = significant_authors
|
225
|
+
end
|
226
|
+
|
227
|
+
if contains_other
|
228
|
+
authors << 'Other'
|
229
|
+
end
|
230
|
+
|
231
|
+
file_path = HTMLGenerator.generate(:history, :data => {:history => buckets.map {|h| h[:authors].merge('Date' => h[:timestamp]) }, :authors => authors.to_a})
|
232
|
+
Launchy.open("file://#{file_path}")
|
233
|
+
end
|
234
|
+
end
|
68
235
|
end
|
69
236
|
end
|
data/templates/all.erb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
<div id='all'></div>
|
2
|
+
|
3
|
+
<script type="text/jsx">
|
4
|
+
/** @jsx React.DOM */
|
5
|
+
|
6
|
+
var lines = <%= all_lines.map(&:to_hash).to_json %>;
|
7
|
+
|
8
|
+
var FilterableTODOList = React.createClass({
|
9
|
+
getInitialState: function() {
|
10
|
+
return {
|
11
|
+
filters: {}
|
12
|
+
};
|
13
|
+
},
|
14
|
+
|
15
|
+
handleFilterChange: function(name, value) {
|
16
|
+
newFilters = this.state.filters;
|
17
|
+
if (value === '') {
|
18
|
+
delete newFilters[name];
|
19
|
+
} else {
|
20
|
+
newFilters[name] = value;
|
21
|
+
}
|
22
|
+
|
23
|
+
this.setState({
|
24
|
+
filters: newFilters
|
25
|
+
});
|
26
|
+
},
|
27
|
+
|
28
|
+
render: function() {
|
29
|
+
return (
|
30
|
+
<div className='filterable-todo-list'>
|
31
|
+
<TODOFilters lines={this.props.lines} filters={this.state.filters} handleFilterChange={this.handleFilterChange} />
|
32
|
+
<TODOLineList lines={this.props.lines} filters={this.state.filters} />
|
33
|
+
</div>
|
34
|
+
);
|
35
|
+
}
|
36
|
+
});
|
37
|
+
|
38
|
+
var TODOFilters = React.createClass({
|
39
|
+
authors: function() {
|
40
|
+
return _.uniq(
|
41
|
+
_.map(this.props.lines, function(line) { return line.author; })
|
42
|
+
);
|
43
|
+
},
|
44
|
+
|
45
|
+
render: function() {
|
46
|
+
var authorFilter = <TODOSelectFilter name='author' options={this.authors()} handleFilterChange={this.props.handleFilterChange} />;
|
47
|
+
|
48
|
+
return (
|
49
|
+
<div className='controls'>
|
50
|
+
<div className='filters'>
|
51
|
+
Filter by:
|
52
|
+
{authorFilter}
|
53
|
+
</div>
|
54
|
+
</div>
|
55
|
+
);
|
56
|
+
}
|
57
|
+
});
|
58
|
+
|
59
|
+
var TODOSelectFilter = React.createClass({
|
60
|
+
selectElement: function() {
|
61
|
+
return this.refs[this.props.name + 'Select'];
|
62
|
+
},
|
63
|
+
|
64
|
+
handleSelect: function() {
|
65
|
+
value = this.selectElement().getDOMNode().value;
|
66
|
+
this.props.handleFilterChange(this.props.name, value);
|
67
|
+
},
|
68
|
+
|
69
|
+
componentDidMount: function() {
|
70
|
+
el = $(this.selectElement().getDOMNode());
|
71
|
+
el.chosen().change(this.handleSelect);
|
72
|
+
},
|
73
|
+
|
74
|
+
render: function() {
|
75
|
+
selectOptions = [];
|
76
|
+
this.props.options.forEach(function(option) {
|
77
|
+
selectOptions.push(<option>{option}</option>);
|
78
|
+
});
|
79
|
+
|
80
|
+
return (
|
81
|
+
<div className='filter'>
|
82
|
+
<select data-placeholder={this.props.name} id={this.props.name} ref={this.props.name + 'Select'}>
|
83
|
+
<option value='' selected>(All authors)</option>
|
84
|
+
{selectOptions}
|
85
|
+
</select>
|
86
|
+
</div>
|
87
|
+
);
|
88
|
+
}
|
89
|
+
});
|
90
|
+
|
91
|
+
var TODOLineList = React.createClass({
|
92
|
+
noFilters: function() {
|
93
|
+
return _.isEmpty(this.props.filters);
|
94
|
+
},
|
95
|
+
|
96
|
+
includeLine: function(line) {
|
97
|
+
filters = this.props.filters;
|
98
|
+
|
99
|
+
if (this.noFilters()) {
|
100
|
+
return true;
|
101
|
+
}
|
102
|
+
|
103
|
+
// If there are any filters, make sure the line fits all of them.
|
104
|
+
return _.every(Object.keys(filters), function(filter) {
|
105
|
+
return line[filter] === filters[filter];
|
106
|
+
});
|
107
|
+
},
|
108
|
+
|
109
|
+
render: function() {
|
110
|
+
var lines = [];
|
111
|
+
this.props.lines.forEach(function(line) {
|
112
|
+
if (this.includeLine(line)) {
|
113
|
+
lines.push(<TODOLine line={line} />);
|
114
|
+
}
|
115
|
+
}.bind(this));
|
116
|
+
|
117
|
+
var countMessage = "";
|
118
|
+
if (this.noFilters()) {
|
119
|
+
countMessage = 'There are ' + lines.length + ' TODOs total!';
|
120
|
+
} else {
|
121
|
+
countMessage = 'There are ' + lines.length + ' TODOs, out of ' + this.props.lines.length + ' total!';
|
122
|
+
}
|
123
|
+
|
124
|
+
return (
|
125
|
+
<div className='lines'>
|
126
|
+
{countMessage}
|
127
|
+
{lines}
|
128
|
+
</div>
|
129
|
+
);
|
130
|
+
}
|
131
|
+
});
|
132
|
+
|
133
|
+
var TODOLine = React.createClass({
|
134
|
+
render: function() {
|
135
|
+
return (
|
136
|
+
<div className='line'>
|
137
|
+
<div className='meta'>
|
138
|
+
<div className='padding'>
|
139
|
+
<span className='date'>{this.props.line.time}</span>
|
140
|
+
<span className='author'>{this.props.line.author}</span>
|
141
|
+
<span className='file'>
|
142
|
+
<a href={'../' + this.props.line.filename}>
|
143
|
+
{this.props.line.filename}:{this.props.line.line_number}
|
144
|
+
</a>
|
145
|
+
</span>
|
146
|
+
</div>
|
147
|
+
</div>
|
148
|
+
<div className='content'>
|
149
|
+
<div className='padding'>
|
150
|
+
<span>{this.props.line.content}</span>
|
151
|
+
</div>
|
152
|
+
</div>
|
153
|
+
</div>
|
154
|
+
);
|
155
|
+
}
|
156
|
+
});
|
157
|
+
|
158
|
+
$(document).ready(function() {
|
159
|
+
React.renderComponent(<FilterableTODOList lines={lines} />, document.getElementById('all'));
|
160
|
+
});
|
161
|
+
</script>
|
@@ -0,0 +1,3 @@
|
|
1
|
+
/* Chosen v1.1.0 | (c) 2011-2013 by Harvest | MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md */
|
2
|
+
|
3
|
+
.chosen-container{position:relative;display:inline-block;vertical-align:middle;font-size:13px;zoom:1;*display:inline;-webkit-user-select:none;-moz-user-select:none;user-select:none}.chosen-container .chosen-drop{position:absolute;top:100%;left:-9999px;z-index:1010;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;border:1px solid #aaa;border-top:0;background:#fff;box-shadow:0 4px 5px rgba(0,0,0,.15)}.chosen-container.chosen-with-drop .chosen-drop{left:0}.chosen-container a{cursor:pointer}.chosen-container-single .chosen-single{position:relative;display:block;overflow:hidden;padding:0 0 0 8px;height:23px;border:1px solid #aaa;border-radius:5px;background-color:#fff;background:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#fff),color-stop(50%,#f6f6f6),color-stop(52%,#eee),color-stop(100%,#f4f4f4));background:-webkit-linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background:-moz-linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background:-o-linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background:linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background-clip:padding-box;box-shadow:0 0 3px #fff inset,0 1px 1px rgba(0,0,0,.1);color:#444;text-decoration:none;white-space:nowrap;line-height:24px}.chosen-container-single .chosen-default{color:#999}.chosen-container-single .chosen-single span{display:block;overflow:hidden;margin-right:26px;text-overflow:ellipsis;white-space:nowrap}.chosen-container-single .chosen-single-with-deselect span{margin-right:38px}.chosen-container-single .chosen-single abbr{position:absolute;top:6px;right:26px;display:block;width:12px;height:12px;background:url(chosen-sprite.png) -42px 1px no-repeat;font-size:1px}.chosen-container-single .chosen-single abbr:hover{background-position:-42px -10px}.chosen-container-single.chosen-disabled .chosen-single abbr:hover{background-position:-42px -10px}.chosen-container-single .chosen-single div{position:absolute;top:0;right:0;display:block;width:18px;height:100%}.chosen-container-single .chosen-single div b{display:block;width:100%;height:100%;background:url(chosen-sprite.png) no-repeat 0 2px}.chosen-container-single .chosen-search{position:relative;z-index:1010;margin:0;padding:3px 4px;white-space:nowrap}.chosen-container-single .chosen-search input[type=text]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:1px 0;padding:4px 20px 4px 5px;width:100%;height:auto;outline:0;border:1px solid #aaa;background:#fff url(chosen-sprite.png) no-repeat 100% -20px;background:url(chosen-sprite.png) no-repeat 100% -20px;font-size:1em;font-family:sans-serif;line-height:normal;border-radius:0}.chosen-container-single .chosen-drop{margin-top:-1px;border-radius:0 0 4px 4px;background-clip:padding-box}.chosen-container-single.chosen-container-single-nosearch .chosen-search{position:absolute;left:-9999px}.chosen-container .chosen-results{position:relative;overflow-x:hidden;overflow-y:auto;margin:0 4px 4px 0;padding:0 0 0 4px;max-height:240px;-webkit-overflow-scrolling:touch}.chosen-container .chosen-results li{display:none;margin:0;padding:5px 6px;list-style:none;line-height:15px;-webkit-touch-callout:none}.chosen-container .chosen-results li.active-result{display:list-item;cursor:pointer}.chosen-container .chosen-results li.disabled-result{display:list-item;color:#ccc;cursor:default}.chosen-container .chosen-results li.highlighted{background-color:#3875d7;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#3875d7),color-stop(90%,#2a62bc));background-image:-webkit-linear-gradient(#3875d7 20%,#2a62bc 90%);background-image:-moz-linear-gradient(#3875d7 20%,#2a62bc 90%);background-image:-o-linear-gradient(#3875d7 20%,#2a62bc 90%);background-image:linear-gradient(#3875d7 20%,#2a62bc 90%);color:#fff}.chosen-container .chosen-results li.no-results{display:list-item;background:#f4f4f4}.chosen-container .chosen-results li.group-result{display:list-item;font-weight:700;cursor:default}.chosen-container .chosen-results li.group-option{padding-left:15px}.chosen-container .chosen-results li em{font-style:normal;text-decoration:underline}.chosen-container-multi .chosen-choices{position:relative;overflow:hidden;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;width:100%;height:auto!important;height:1%;border:1px solid #aaa;background-color:#fff;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(1%,#eee),color-stop(15%,#fff));background-image:-webkit-linear-gradient(#eee 1%,#fff 15%);background-image:-moz-linear-gradient(#eee 1%,#fff 15%);background-image:-o-linear-gradient(#eee 1%,#fff 15%);background-image:linear-gradient(#eee 1%,#fff 15%);cursor:text}.chosen-container-multi .chosen-choices li{float:left;list-style:none}.chosen-container-multi .chosen-choices li.search-field{margin:0;padding:0;white-space:nowrap}.chosen-container-multi .chosen-choices li.search-field input[type=text]{margin:1px 0;padding:5px;height:15px;outline:0;border:0!important;background:transparent!important;box-shadow:none;color:#666;font-size:100%;font-family:sans-serif;line-height:normal;border-radius:0}.chosen-container-multi .chosen-choices li.search-field .default{color:#999}.chosen-container-multi .chosen-choices li.search-choice{position:relative;margin:3px 0 3px 5px;padding:3px 20px 3px 5px;border:1px solid #aaa;border-radius:3px;background-color:#e4e4e4;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),color-stop(100%,#eee));background-image:-webkit-linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-moz-linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-o-linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-clip:padding-box;box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);color:#333;line-height:13px;cursor:default}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close{position:absolute;top:4px;right:3px;display:block;width:12px;height:12px;background:url(chosen-sprite.png) -42px 1px no-repeat;font-size:1px}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover{background-position:-42px -10px}.chosen-container-multi .chosen-choices li.search-choice-disabled{padding-right:5px;border:1px solid #ccc;background-color:#e4e4e4;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),color-stop(100%,#eee));background-image:-webkit-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-moz-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-o-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);color:#666}.chosen-container-multi .chosen-choices li.search-choice-focus{background:#d4d4d4}.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close{background-position:-42px -10px}.chosen-container-multi .chosen-results{margin:0;padding:0}.chosen-container-multi .chosen-drop .result-selected{display:list-item;color:#ccc;cursor:default}.chosen-container-active .chosen-single{border:1px solid #5897fb;box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active.chosen-with-drop .chosen-single{border:1px solid #aaa;-moz-border-radius-bottomright:0;border-bottom-right-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#eee),color-stop(80%,#fff));background-image:-webkit-linear-gradient(#eee 20%,#fff 80%);background-image:-moz-linear-gradient(#eee 20%,#fff 80%);background-image:-o-linear-gradient(#eee 20%,#fff 80%);background-image:linear-gradient(#eee 20%,#fff 80%);box-shadow:0 1px 0 #fff inset}.chosen-container-active.chosen-with-drop .chosen-single div{border-left:0;background:transparent}.chosen-container-active.chosen-with-drop .chosen-single div b{background-position:-18px 2px}.chosen-container-active .chosen-choices{border:1px solid #5897fb;box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active .chosen-choices li.search-field input[type=text]{color:#111!important}.chosen-disabled{opacity:.5!important;cursor:default}.chosen-disabled .chosen-single{cursor:default}.chosen-disabled .chosen-choices .search-choice .search-choice-close{cursor:default}.chosen-rtl{text-align:right}.chosen-rtl .chosen-single{overflow:visible;padding:0 8px 0 0}.chosen-rtl .chosen-single span{margin-right:0;margin-left:26px;direction:rtl}.chosen-rtl .chosen-single-with-deselect span{margin-left:38px}.chosen-rtl .chosen-single div{right:auto;left:3px}.chosen-rtl .chosen-single abbr{right:auto;left:26px}.chosen-rtl .chosen-choices li{float:right}.chosen-rtl .chosen-choices li.search-field input[type=text]{direction:rtl}.chosen-rtl .chosen-choices li.search-choice{margin:3px 5px 3px 0;padding:3px 5px 3px 19px}.chosen-rtl .chosen-choices li.search-choice .search-choice-close{right:auto;left:4px}.chosen-rtl.chosen-container-single-nosearch .chosen-search,.chosen-rtl .chosen-drop{left:9999px}.chosen-rtl.chosen-container-single .chosen-results{margin:0 0 4px 4px;padding:0 4px 0 0}.chosen-rtl .chosen-results li.group-option{padding-right:15px;padding-left:0}.chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div{border-right:0}.chosen-rtl .chosen-search input[type=text]{padding:4px 5px 4px 20px;background:#fff url(chosen-sprite.png) no-repeat -30px -20px;background:url(chosen-sprite.png) no-repeat -30px -20px;direction:rtl}.chosen-rtl.chosen-container-single .chosen-single div b{background-position:6px 2px}.chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b{background-position:-12px 2px}@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-resolution:144dpi){.chosen-rtl .chosen-search input[type=text],.chosen-container-single .chosen-single abbr,.chosen-container-single .chosen-single div b,.chosen-container-single .chosen-search input[type=text],.chosen-container-multi .chosen-choices .search-choice .search-choice-close,.chosen-container .chosen-results-scroll-down span,.chosen-container .chosen-results-scroll-up span{background-image:url(chosen-sprite@2x.png)!important;background-size:52px 37px!important;background-repeat:no-repeat!important}}
|
@@ -0,0 +1,129 @@
|
|
1
|
+
/* Global */
|
2
|
+
|
3
|
+
html, body {
|
4
|
+
margin: 0;
|
5
|
+
padding: 0;
|
6
|
+
}
|
7
|
+
|
8
|
+
body {
|
9
|
+
font-family: Helvetica Neue, Arial, san-serif;
|
10
|
+
font-size: 0.9em;
|
11
|
+
color: #444;
|
12
|
+
|
13
|
+
text-rendering: optimizeLegibility;
|
14
|
+
-webkit-font-smoothing: antialiased;
|
15
|
+
}
|
16
|
+
|
17
|
+
body a {
|
18
|
+
color: #5F8EB3;
|
19
|
+
font-weight: bold;
|
20
|
+
text-decoration: none;
|
21
|
+
}
|
22
|
+
|
23
|
+
body a:hover {
|
24
|
+
text-decoration: underline;
|
25
|
+
}
|
26
|
+
|
27
|
+
/* Layout */
|
28
|
+
|
29
|
+
header {
|
30
|
+
background: #333;
|
31
|
+
color: #fff;
|
32
|
+
padding: 10px 30px;
|
33
|
+
}
|
34
|
+
|
35
|
+
header h1 {
|
36
|
+
-webkit-margin-before: 0;
|
37
|
+
-webkit-margin-after: 0;
|
38
|
+
}
|
39
|
+
|
40
|
+
section.content {
|
41
|
+
padding: 30px;
|
42
|
+
}
|
43
|
+
|
44
|
+
footer {
|
45
|
+
text-align: center;
|
46
|
+
padding-bottom: 25px;
|
47
|
+
}
|
48
|
+
|
49
|
+
/* TODO Lines */
|
50
|
+
|
51
|
+
section div.controls {
|
52
|
+
padding-bottom: 20px;
|
53
|
+
}
|
54
|
+
|
55
|
+
section div.controls div.filter {
|
56
|
+
display: inline-block;
|
57
|
+
padding-left: 10px;
|
58
|
+
}
|
59
|
+
|
60
|
+
section div.controls div.filter select {
|
61
|
+
width: 150px;
|
62
|
+
}
|
63
|
+
|
64
|
+
section div.line {
|
65
|
+
width: 100%;
|
66
|
+
}
|
67
|
+
|
68
|
+
section div.line > div {
|
69
|
+
display: inline-block;
|
70
|
+
height: 38px;
|
71
|
+
line-height: 20px;
|
72
|
+
|
73
|
+
margin: 2px 0;
|
74
|
+
|
75
|
+
border-style: solid;
|
76
|
+
border-width: 1px 1px 5px;
|
77
|
+
}
|
78
|
+
|
79
|
+
section div.line div div.padding {
|
80
|
+
padding: 10px 10px;
|
81
|
+
|
82
|
+
white-space: nowrap;
|
83
|
+
overflow: hidden;
|
84
|
+
text-overflow: ellipsis;
|
85
|
+
}
|
86
|
+
|
87
|
+
section div.line div.meta {
|
88
|
+
background: #B1D2E3;
|
89
|
+
border-color: #A1C3D6;
|
90
|
+
border-radius: 2px 0px 0px 2px;
|
91
|
+
|
92
|
+
font-weight: bold;
|
93
|
+
|
94
|
+
width: 35%;
|
95
|
+
}
|
96
|
+
|
97
|
+
section div.line div.content {
|
98
|
+
background: #f6f6f6;
|
99
|
+
border-color: #eee;
|
100
|
+
border-radius: 0 2px 3px 0;
|
101
|
+
|
102
|
+
font-family: 'Monaco', serif;
|
103
|
+
font-size: 0.8em;
|
104
|
+
|
105
|
+
position: relative;
|
106
|
+
left: -5px;
|
107
|
+
z-index: -1;
|
108
|
+
padding-left: 15px;
|
109
|
+
|
110
|
+
width: 60%;
|
111
|
+
}
|
112
|
+
|
113
|
+
section div.line:hover div.meta {
|
114
|
+
background: #A1C3D6;
|
115
|
+
border-color: #95B9CC;
|
116
|
+
}
|
117
|
+
|
118
|
+
section div.line:hover div.content{
|
119
|
+
background: #eee;
|
120
|
+
border-color: #dedede;
|
121
|
+
}
|
122
|
+
|
123
|
+
section div.line div.meta span {
|
124
|
+
margin-right: 10px;
|
125
|
+
}
|
126
|
+
|
127
|
+
section div.line div.meta span:last-child {
|
128
|
+
margin-right: 0;
|
129
|
+
}
|