todidnt 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|