notes-cli 1.1.0 → 2.0.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.
- checksums.yaml +6 -14
- data/.gitignore +44 -0
- data/.ruby-gemset +1 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +71 -0
- data/bin/notes +17 -1
- data/config.ru +4 -0
- data/lib/notes-cli.rb +111 -1
- data/lib/notes-cli/cli.rb +10 -52
- data/lib/notes-cli/{opts.rb → options.rb} +27 -10
- data/lib/notes-cli/server.rb +34 -0
- data/lib/notes-cli/stats.rb +40 -0
- data/lib/notes-cli/tasks.rb +170 -0
- data/lib/notes-cli/version.rb +1 -1
- data/lib/notes-cli/web.rb +39 -0
- data/notes-cli.gemspec +23 -0
- data/spec/notes_spec.rb +12 -0
- data/spec/options_spec.rb +125 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/stats_spec.rb +34 -0
- data/spec/tasks_spec.rb +90 -0
- data/web/assets/javascripts/application.js +438 -0
- data/web/assets/javascripts/lib/backbone.min.js +1 -0
- data/web/assets/javascripts/lib/d3.min.js +5 -0
- data/web/assets/javascripts/lib/jquery-1.10.2.min.js +5 -0
- data/web/assets/javascripts/lib/underscore.min.js +5 -0
- data/web/assets/stylesheets/application.css +359 -0
- data/web/assets/stylesheets/reset.css +48 -0
- data/web/views/index.erb +40 -0
- data/web/views/layout.erb +92 -0
- metadata +53 -13
data/spec/spec_helper.rb
ADDED
data/spec/stats_spec.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe 'stats' do
|
5
|
+
context 'compute' do
|
6
|
+
let(:file) { Tempfile.new('example') }
|
7
|
+
let(:options) { Notes::Options.defaults }
|
8
|
+
let(:tasks) { Notes::Tasks.for_file(file.path, options[:flags]) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
File.open(file, 'w') do |f|
|
12
|
+
f.write "TODO: one\n"
|
13
|
+
f.write "two\n"
|
14
|
+
f.write "TODO: three\n"
|
15
|
+
f.write "OPTIMIZE: four\n"
|
16
|
+
f.write "five\n"
|
17
|
+
f.write "six\n"
|
18
|
+
f.write "seven FIXME\n"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'counts stats correctly' do
|
23
|
+
Notes::Stats.flag_counts(tasks).should == {
|
24
|
+
'TODO' => 2,
|
25
|
+
'OPTIMIZE' => 1,
|
26
|
+
'FIXME' => 1
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'aggregates found flags' do
|
31
|
+
Notes::Stats.found_flags(tasks).should == ['TODO', 'OPTIMIZE', 'FIXME']
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/spec/tasks_spec.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe 'tasks' do
|
5
|
+
|
6
|
+
context 'matching_flags' do
|
7
|
+
specify do
|
8
|
+
Notes::Tasks.matching_flags("TODO: foo", ["TODO"]).should == ["TODO"]
|
9
|
+
end
|
10
|
+
|
11
|
+
specify do
|
12
|
+
Notes::Tasks.matching_flags("TODO: bar", ["FIXME"]).should == []
|
13
|
+
end
|
14
|
+
|
15
|
+
specify do
|
16
|
+
Notes::Tasks.matching_flags("fixme", ["FIXME", "foo"]).should == ["FIXME"]
|
17
|
+
end
|
18
|
+
|
19
|
+
specify do
|
20
|
+
Notes::Tasks.matching_flags("TODO: foo FIXME", ["TODO","FIXME", "OPTIMIZE"])
|
21
|
+
.should == ["TODO","FIXME"]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'for_file' do
|
26
|
+
let(:file) { Tempfile.new('example') }
|
27
|
+
let(:options) { Notes::Options.defaults }
|
28
|
+
let(:tasks) { Notes::Tasks.for_file(file.path, options[:flags]) }
|
29
|
+
|
30
|
+
before do
|
31
|
+
File.open(file, 'w') do |f|
|
32
|
+
f.write "TODO: one\n"
|
33
|
+
f.write "two\n"
|
34
|
+
f.write "three\n"
|
35
|
+
f.write "findme: four\n"
|
36
|
+
f.write "five\n"
|
37
|
+
f.write "six\n"
|
38
|
+
f.write "seven FIXME\n"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
specify { tasks.length.should == 2 }
|
43
|
+
|
44
|
+
it 'counts custom flags correctly' do
|
45
|
+
tasks = Notes::Tasks.for_file(file.path, options[:flags] + ["FINDME"])
|
46
|
+
tasks.length.should == 3
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'parses lines correctly' do
|
50
|
+
tasks = Notes::Tasks.for_file(file.path, options[:flags] + ["FINDME"])
|
51
|
+
t0, t1, t2 = tasks
|
52
|
+
|
53
|
+
t0.line_num.should == 1
|
54
|
+
t0.line.should == "TODO: one"
|
55
|
+
t0.flags.should == ["TODO"]
|
56
|
+
t0.context.should == "two\nthree\nfindme: four\nfive\nsix"
|
57
|
+
|
58
|
+
t1.line_num.should == 4
|
59
|
+
t1.line.should == "findme: four"
|
60
|
+
t1.flags.should == ["FINDME"]
|
61
|
+
t1.context.should == "five\nsix\nseven FIXME"
|
62
|
+
|
63
|
+
t2.line_num.should == 7
|
64
|
+
t2.line.should == "seven FIXME"
|
65
|
+
t2.flags.should == ["FIXME"]
|
66
|
+
t2.context.should == ""
|
67
|
+
end
|
68
|
+
|
69
|
+
# This is kind of annoying - we have a perfectly valid file to parse,
|
70
|
+
# but it's not in a git repo. We could build an abstraction layer over
|
71
|
+
# the git call, but that doesn't seem worth it, so hand-wave the git parts
|
72
|
+
# and test simple field parsing
|
73
|
+
#
|
74
|
+
# Also directly testing private methods, somebody call the TDD police
|
75
|
+
it 'reads information from git' do
|
76
|
+
Notes.should_receive(:blame).and_return({
|
77
|
+
"author" => "Andrew Berls",
|
78
|
+
"author-time" => "1381862180",
|
79
|
+
"sha" => "705690fb747a2ae82a96edb21df7e424f5cc518b",
|
80
|
+
})
|
81
|
+
|
82
|
+
info = Notes::Tasks.send(:line_info, 'doesnt_matter_stubbed_out', 0)
|
83
|
+
info[:author].should == 'Andrew Berls'
|
84
|
+
info[:date].should == '2013-10-15 11:36:20 -0700'
|
85
|
+
info[:sha].should == '705690fb747a2ae82a96edb21df7e424f5cc518b'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
@@ -0,0 +1,438 @@
|
|
1
|
+
// Change underscore templates to {{}} and {{= }} to play nice with ERB
|
2
|
+
_.templateSettings = {
|
3
|
+
interpolate: /\{\{\=(.+?)\}\}/g,
|
4
|
+
evaluate: /\{\{(.+?)\}\}/g
|
5
|
+
};
|
6
|
+
|
7
|
+
|
8
|
+
// Global namespace object
|
9
|
+
window.Notes = {};
|
10
|
+
|
11
|
+
|
12
|
+
Notes.escapeHtml = function(text) {
|
13
|
+
return $('<div>').text(text).html();
|
14
|
+
}
|
15
|
+
|
16
|
+
|
17
|
+
// Is Array A a subset of Array B?
|
18
|
+
Notes.isSubset = function(a,b) {
|
19
|
+
return a.length === _.intersection(a,b).length;
|
20
|
+
}
|
21
|
+
|
22
|
+
|
23
|
+
// How many tabs or spaces does a string start with?
|
24
|
+
Notes.leadingWhitespaceCount = function(str) {
|
25
|
+
var count = 0;
|
26
|
+
while(str.charAt(0) === " " || str.charAt(0) === "\t") {
|
27
|
+
str = str.substr(1);
|
28
|
+
count++;
|
29
|
+
}
|
30
|
+
return count;
|
31
|
+
}
|
32
|
+
|
33
|
+
|
34
|
+
// Take an array of lines and return the smallest number of leading whitespaces
|
35
|
+
// (tabs or spaces) from among them
|
36
|
+
Notes.minLtrim = function(lines) {
|
37
|
+
var counts = lines.map(function(line) { return Notes.leadingWhitespaceCount(line); })
|
38
|
+
return Math.min.apply(null, counts);
|
39
|
+
}
|
40
|
+
|
41
|
+
|
42
|
+
Notes.defaultFlags = ['TODO', 'OPTIMIZE', 'FIXME'];
|
43
|
+
|
44
|
+
|
45
|
+
Notes.getSelectedFlags = function() {
|
46
|
+
return Notes.sidebarView.collection
|
47
|
+
.filter(function(f) { return f.get('checked'); })
|
48
|
+
.map(function(f) { return f.get('name') });
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
// Color classes to be paired against distinct flags (for consistency)
|
53
|
+
Notes.colors = [
|
54
|
+
'lightblue','purple','fuschia','lightgreen','orange','green','blue',
|
55
|
+
'pink','turquoise','deepred',
|
56
|
+
]
|
57
|
+
|
58
|
+
// TODO
|
59
|
+
Notes.buildColorMap = function(flags) {
|
60
|
+
var allFlags = _.uniq(flags.concat(Notes.defaultFlags));
|
61
|
+
Notes.colorMap = _.zip(allFlags, Notes.colors);
|
62
|
+
}
|
63
|
+
|
64
|
+
|
65
|
+
Notes.colorFor = function(flagName) {
|
66
|
+
var map;
|
67
|
+
for (var i=0; i<Notes.colorMap.length; i++) {
|
68
|
+
map = Notes.colorMap[i];
|
69
|
+
if (map[0] === flagName) {
|
70
|
+
return map[1];
|
71
|
+
} else if (map[0] === undefined) {
|
72
|
+
// No existing mapping found - add new flag to colorMap
|
73
|
+
map[0] = flagName;
|
74
|
+
return map[1];
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
return Notes.colors[Notes.colors.length-1]; // TODO - default to last color in list
|
79
|
+
}
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
Notes.Task = Backbone.Model.extend({
|
84
|
+
escapedLine: function() {
|
85
|
+
return Notes.escapeHtml(this.get('line'));
|
86
|
+
},
|
87
|
+
|
88
|
+
escapedContextLines: function() {
|
89
|
+
var ctx = this.get('context');
|
90
|
+
if (ctx === '') { return []; }
|
91
|
+
|
92
|
+
return ctx.split("\n")
|
93
|
+
.map(function(line) { return Notes.escapeHtml(line); });
|
94
|
+
},
|
95
|
+
|
96
|
+
allLines: function() {
|
97
|
+
return [this.escapedLine()].concat(this.escapedContextLines());
|
98
|
+
},
|
99
|
+
|
100
|
+
highlightedLine: function() {
|
101
|
+
var regex = new RegExp(this.get('flags').join('|'), 'gi');
|
102
|
+
return this.escapedLine().replace(regex, function(flag) {
|
103
|
+
return "<strong>"+flag+"</strong>";
|
104
|
+
});
|
105
|
+
},
|
106
|
+
|
107
|
+
formattedSha: function() {
|
108
|
+
var sha = this.get('sha');
|
109
|
+
return sha ? "@ " + sha.slice(0,7) : '';
|
110
|
+
},
|
111
|
+
|
112
|
+
formattedDate: function() {
|
113
|
+
var date = new Date(this.get('date')),
|
114
|
+
month = date.getMonth() + 1,
|
115
|
+
day = date.getDate(),
|
116
|
+
year = date.getFullYear().toString().slice(2);
|
117
|
+
|
118
|
+
return month + '/' + day + '/' + year;
|
119
|
+
}
|
120
|
+
});
|
121
|
+
|
122
|
+
|
123
|
+
// A view for a single task item
|
124
|
+
Notes.TaskView = Backbone.View.extend({
|
125
|
+
tagName: 'div',
|
126
|
+
className: 'task',
|
127
|
+
template: _.template($('#tmpl-task').html()),
|
128
|
+
|
129
|
+
render: function() {
|
130
|
+
$(this.el).html(this.template({ task: this.model }));
|
131
|
+
return this;
|
132
|
+
},
|
133
|
+
|
134
|
+
events: {
|
135
|
+
'click .task-toggle': 'toggleContext'
|
136
|
+
},
|
137
|
+
|
138
|
+
toggleContext: function() {
|
139
|
+
var $el = $(this.el),
|
140
|
+
$toggle = $el.find('.task-toggle'),
|
141
|
+
$ctx = $el.find('.task-context');
|
142
|
+
|
143
|
+
if ($ctx.is(':visible')) {
|
144
|
+
$toggle.removeClass('fa-angle-up').addClass('fa-angle-down');
|
145
|
+
$ctx.slideUp();
|
146
|
+
} else {
|
147
|
+
$toggle.removeClass('fa-angle-down').addClass('fa-angle-up');
|
148
|
+
$ctx.slideDown();
|
149
|
+
}
|
150
|
+
}
|
151
|
+
});
|
152
|
+
|
153
|
+
|
154
|
+
Notes.TasksCollection = Backbone.Collection.extend({
|
155
|
+
model: Notes.Task,
|
156
|
+
|
157
|
+
initialize: function() {
|
158
|
+
this.filename = '';
|
159
|
+
}
|
160
|
+
});
|
161
|
+
|
162
|
+
|
163
|
+
// A view for a collection of tasks grouped under a common filename
|
164
|
+
Notes.TaskCollectionView = Backbone.View.extend({
|
165
|
+
tagName: 'div',
|
166
|
+
classname: 'tasks-container',
|
167
|
+
|
168
|
+
render: function() {
|
169
|
+
var $el = $(this.el);
|
170
|
+
$el.append("<h2 class='task-filename'>"+this.collection.filename+":</h2>");
|
171
|
+
|
172
|
+
this.collection.each(function(task) {
|
173
|
+
$el.append(new Notes.TaskView({ model: task }).render().el);
|
174
|
+
});
|
175
|
+
return this;
|
176
|
+
}
|
177
|
+
});
|
178
|
+
|
179
|
+
|
180
|
+
|
181
|
+
// A flag accompanied by a checkbox in the sidebar
|
182
|
+
Notes.Flag = Backbone.Model.extend({
|
183
|
+
defaults: { checked: true },
|
184
|
+
|
185
|
+
checkedClass: function() { return this.get('checked') ? 'checked' : ''; }
|
186
|
+
});
|
187
|
+
|
188
|
+
|
189
|
+
Notes.FlagView = Backbone.View.extend({
|
190
|
+
tagName: 'div',
|
191
|
+
className: 'flag-container',
|
192
|
+
template: _.template($('#tmpl-flag').html()),
|
193
|
+
|
194
|
+
render: function() {
|
195
|
+
$(this.el).html(this.template({ flag: this.model }));
|
196
|
+
return this;
|
197
|
+
},
|
198
|
+
|
199
|
+
events: {
|
200
|
+
'click .checkbox': 'toggleCheck',
|
201
|
+
'click .delete-flag-btn': 'deleteFlag'
|
202
|
+
},
|
203
|
+
|
204
|
+
$checkbox: function() { return $(this.el).find('.checkbox'); },
|
205
|
+
isChecked: function() { return this.$checkbox().hasClass('checked'); },
|
206
|
+
check: function() {
|
207
|
+
this.model.set('checked', true);
|
208
|
+
this.$checkbox().addClass('checked');
|
209
|
+
},
|
210
|
+
uncheck: function() {
|
211
|
+
this.model.set('checked', false);
|
212
|
+
this.$checkbox().removeClass('checked');
|
213
|
+
},
|
214
|
+
toggleCheck: function() { this.isChecked() ? this.uncheck() : this.check(); },
|
215
|
+
|
216
|
+
deleteFlag: function() {
|
217
|
+
Notes.sidebarView.collection.remove(this.model);
|
218
|
+
var $el = $(this.el);
|
219
|
+
$el.slideUp(200, function() { $el.remove(); });
|
220
|
+
}
|
221
|
+
});
|
222
|
+
|
223
|
+
|
224
|
+
Notes.FlagCollection = Backbone.Collection.extend({
|
225
|
+
model: Notes.Flag,
|
226
|
+
|
227
|
+
// Add a flag into the collection unless it's already present
|
228
|
+
merge: function(flag) {
|
229
|
+
var attrs = { name: flag.toUpperCase() }
|
230
|
+
match = this.findWhere(attrs);
|
231
|
+
if (!match) { this.add(attrs); }
|
232
|
+
}
|
233
|
+
});
|
234
|
+
|
235
|
+
|
236
|
+
Notes.FlagCollectionView = Backbone.View.extend({
|
237
|
+
tagName: 'div',
|
238
|
+
|
239
|
+
render: function() {
|
240
|
+
var $el = $(this.el);
|
241
|
+
$el.html('');
|
242
|
+
|
243
|
+
this.collection.each(function(flag) {
|
244
|
+
$el.append(new Notes.FlagView({ model: flag }).render().el);
|
245
|
+
});
|
246
|
+
return this;
|
247
|
+
}
|
248
|
+
});
|
249
|
+
|
250
|
+
|
251
|
+
|
252
|
+
// Merge a custom flag into the sidebar and re-render
|
253
|
+
Notes.addFlag = function(flag) {
|
254
|
+
if (flag === '') { return false; }
|
255
|
+
Notes.sidebarView.collection.merge(flag);
|
256
|
+
Notes.renderSidebar();
|
257
|
+
}
|
258
|
+
|
259
|
+
|
260
|
+
// Build (or rebuild) the sidebar from the flags
|
261
|
+
// used to query the server
|
262
|
+
Notes.buildSidebar = function(flags) {
|
263
|
+
var attrs = flags.map(function(f) { return { name: f }; });
|
264
|
+
|
265
|
+
Notes.sidebarView = new Notes.FlagCollectionView({
|
266
|
+
collection: new Notes.FlagCollection(attrs)
|
267
|
+
});
|
268
|
+
Notes.renderSidebar();
|
269
|
+
}
|
270
|
+
|
271
|
+
// TODO: calling this more than once breaks click handlers ???
|
272
|
+
Notes.renderSidebar = function() {
|
273
|
+
var $container = $('.flags-container');
|
274
|
+
$container.empty();
|
275
|
+
$container.append(Notes.sidebarView.render().el);
|
276
|
+
}
|
277
|
+
|
278
|
+
|
279
|
+
Notes.renderStats = function(stats) {
|
280
|
+
var flagCounts = stats.flag_counts;
|
281
|
+
|
282
|
+
// TODO: add in stats container
|
283
|
+
// <div class="stats-container">
|
284
|
+
// <div class="chart"></div>
|
285
|
+
// </div>
|
286
|
+
}
|
287
|
+
|
288
|
+
|
289
|
+
// tasks - Array[Notes.Task]
|
290
|
+
Notes.renderTasks = function(tasks) {
|
291
|
+
var $container = $('.main-content-container'),
|
292
|
+
filename, collection, collectionView;
|
293
|
+
|
294
|
+
$container.empty();
|
295
|
+
|
296
|
+
if (tasks.length === 0) {
|
297
|
+
$container.html($("<div class='empty-tasks-container'>").append(
|
298
|
+
"<h2>No tasks matching the criteria were found!</h2>"));
|
299
|
+
return;
|
300
|
+
}
|
301
|
+
|
302
|
+
// filename -> Array[Notes.Task]
|
303
|
+
var taskMap = _.groupBy(tasks, function(t) { return t.get('filename'); });
|
304
|
+
|
305
|
+
for (filename in taskMap) {
|
306
|
+
collection = new Notes.TasksCollection(taskMap[filename]);
|
307
|
+
collection.filename = filename;
|
308
|
+
|
309
|
+
collectionView = new Notes.TaskCollectionView({ collection: collection })
|
310
|
+
$container.append(collectionView.render().el);
|
311
|
+
}
|
312
|
+
}
|
313
|
+
|
314
|
+
|
315
|
+
Notes.addProgress = function() {
|
316
|
+
$('.loading-container').find('p').append('.');
|
317
|
+
}
|
318
|
+
|
319
|
+
|
320
|
+
|
321
|
+
// Check if a set of tasks can be filtered based on flags we've already searched for
|
322
|
+
// This allows us to avoid hitting the server when we don't need to
|
323
|
+
//
|
324
|
+
// flags - Array[String]
|
325
|
+
Notes.isSubsetQuery = function(flags) {
|
326
|
+
return Notes.isSubset(flags, Notes.lastQueryFlags);
|
327
|
+
}
|
328
|
+
|
329
|
+
|
330
|
+
// Find a subset of locally-cached tasks that match a set of search flags
|
331
|
+
// Returns Array[Notes.Task]
|
332
|
+
//
|
333
|
+
// TODO: this behaves weirdly if a task has multiple flags, punting for now
|
334
|
+
Notes.filterTasks = function(queryFlags) {
|
335
|
+
if (queryFlags.length === 0) { return []; }
|
336
|
+
|
337
|
+
return Notes.tasks.filter(function(task) {
|
338
|
+
var taskFlags = task.get('flags');
|
339
|
+
return (Notes.isSubset(taskFlags, queryFlags) ||
|
340
|
+
Notes.isSubset(queryFlags, taskFlags));
|
341
|
+
});
|
342
|
+
}
|
343
|
+
|
344
|
+
|
345
|
+
// Returns the URL to query the server at
|
346
|
+
Notes.queryPath = function() {
|
347
|
+
var path = window.location.pathname;
|
348
|
+
return (path === '/' ? '' : path) + "/tasks.json"
|
349
|
+
}
|
350
|
+
|
351
|
+
|
352
|
+
// Fetch tasks from the server and re-render
|
353
|
+
Notes.queryTasks = function(queryFlags) {
|
354
|
+
if (!Notes.colorMap) { Notes.buildColorMap(queryFlags); }
|
355
|
+
|
356
|
+
if (Notes.lastQueryFlags && Notes.isSubsetQuery(queryFlags)) {
|
357
|
+
// Subset query - don't need to hit server
|
358
|
+
var tasks = Notes.filterTasks(queryFlags);
|
359
|
+
Notes.renderTasks(tasks);
|
360
|
+
return;
|
361
|
+
}
|
362
|
+
|
363
|
+
// Can't filter client-side - need to hit server
|
364
|
+
$('.main-content-container').html("<div class='loading-container'><p>Loading </p></div>");
|
365
|
+
var progressInterval = setInterval(Notes.addProgress, 175);
|
366
|
+
|
367
|
+
if (!Notes.sidebarView) { Notes.buildSidebar(queryFlags); }
|
368
|
+
|
369
|
+
$.getJSON(Notes.queryPath(), { flags: queryFlags }, function(json) {
|
370
|
+
var stats = json.stats,
|
371
|
+
tasks = json.tasks.map(function(attrs) { return new Notes.Task(attrs) });
|
372
|
+
|
373
|
+
Notes.tasks = tasks;
|
374
|
+
Notes.lastQueryFlags = queryFlags; // Save the most recent search terms for checking subsets
|
375
|
+
|
376
|
+
clearInterval(progressInterval);
|
377
|
+
Notes.renderStats(stats);
|
378
|
+
Notes.renderTasks(tasks);
|
379
|
+
|
380
|
+
});
|
381
|
+
}
|
382
|
+
|
383
|
+
|
384
|
+
|
385
|
+
// Page Load
|
386
|
+
// ----------------------------------
|
387
|
+
Notes.queryTasks(Notes.defaultFlags);
|
388
|
+
|
389
|
+
|
390
|
+
$(function() {
|
391
|
+
var $doc = $(document);
|
392
|
+
|
393
|
+
$doc.on('keyup', '.add-flag', function(e) {
|
394
|
+
if (e.keyCode === 13) {
|
395
|
+
var $input = $(this);
|
396
|
+
Notes.addFlag($input.val());
|
397
|
+
$input.val('');
|
398
|
+
}
|
399
|
+
});
|
400
|
+
|
401
|
+
|
402
|
+
$doc.on('click', '.add-flag-btn', function() {
|
403
|
+
var $input = $('.add-flag');
|
404
|
+
Notes.addFlag($input.val());
|
405
|
+
$input.val('');
|
406
|
+
return false;
|
407
|
+
});
|
408
|
+
|
409
|
+
|
410
|
+
$doc.on('click', '.filter-btn', function() {
|
411
|
+
Notes.queryTasks(Notes.getSelectedFlags());
|
412
|
+
return false;
|
413
|
+
});
|
414
|
+
|
415
|
+
|
416
|
+
$doc.on('click', '.restore-defaults-btn', function() {
|
417
|
+
Notes.queryTasks(Notes.defaultFlags);
|
418
|
+
return false;
|
419
|
+
});
|
420
|
+
|
421
|
+
|
422
|
+
$doc.on('click', '.toggle-all-context-btn', function() {
|
423
|
+
var $toggle = $('.task-toggle'),
|
424
|
+
$ctx = $('.task-context');
|
425
|
+
|
426
|
+
if ($ctx.is(':visible')) {
|
427
|
+
$(this).html("Show all context <i class='fa fa-angle-down'></i>");
|
428
|
+
$toggle.removeClass('fa-angle-up').addClass('fa-angle-down');
|
429
|
+
$ctx.slideUp();
|
430
|
+
} else {
|
431
|
+
$(this).html("Hide all context <i class='fa fa-angle-up'></i>");
|
432
|
+
$toggle.removeClass('fa-angle-down').addClass('fa-angle-up');
|
433
|
+
$ctx.slideDown();
|
434
|
+
}
|
435
|
+
|
436
|
+
return false;
|
437
|
+
});
|
438
|
+
});
|