challenges: better unlinkig of tasks on unsubscribe

This commit is contained in:
Tyler Renelle
2013-10-27 10:38:28 -07:00
parent fa25f3d300
commit d60a56432f
9 changed files with 112 additions and 266 deletions

View File

@@ -168,52 +168,13 @@ habitrpg.controller("ChallengesCtrl", ['$scope', '$rootScope', 'User', 'Challeng
-------------------------- --------------------------
*/ */
$scope.taskUnsubscribe = function(e, el) { $scope.unlink = function(task, keep) {
/* // TODO move this to userServices, turn userSerivces.user into ng-resource
since the challenge was deleted, we don't have its data to unsubscribe from - but we have the vestiges on the task $http.post(API_URL + '/api/v1/user/task/' + task.id + '/unlink', {keep:keep})
FIXME this is a really dumb way of doing this .success(function(){
*/ debugger
User.log({});
var deletedChal, i, path, tasks, tobj; });
tasks = this.priv.get('tasks');
tobj = tasks[$(el).attr("data-tid")];
deletedChal = {
id: tobj.challenge,
members: [this.uid],
habits: _.where(tasks, {
type: 'habit',
challenge: tobj.challenge
}),
dailys: _.where(tasks, {
type: 'daily',
challenge: tobj.challenge
}),
todos: _.where(tasks, {
type: 'todo',
challenge: tobj.challenge
}),
rewards: _.where(tasks, {
type: 'reward',
challenge: tobj.challenge
})
};
switch ($(el).attr('data-action')) {
case 'keep':
this.priv.del("tasks." + tobj.id + ".challenge");
return this.priv.del("tasks." + tobj.id + ".group");
case 'keep-all':
return app.challenges.unsubscribe.call(this, deletedChal, true);
case 'remove':
path = "_page.lists.tasks." + this.uid + "." + tobj.type + "s";
if (~(i = _.findIndex(this.model.get(path), {
id: tobj.id
}))) {
return this.model.remove(path, i);
}
break;
case 'remove-all':
return app.challenges.unsubscribe.call(this, deletedChal, false);
}
}; };
$scope.unsubscribe = function(keep) { $scope.unsubscribe = function(keep) {
@@ -228,7 +189,7 @@ habitrpg.controller("ChallengesCtrl", ['$scope', '$rootScope', 'User', 'Challeng
$scope.selectedChal = chal; $scope.selectedChal = chal;
$scope.popoverEl = $($event.target); $scope.popoverEl = $($event.target);
var html = $compile( var html = $compile(
'<a ng-controller="ChallengesCtrl" ng-click="unsubscribe(false)">Remove Tasks</a><br/>\n<a ng-click="unsubscribe(true)">Keep Tasks</a><br/>\n<a ng-click="unsubscribe(\'cancel\')">Cancel</a><br/>' '<a ng-controller="ChallengesCtrl" ng-click="unsubscribe(\'remove-all\')">Remove Tasks</a><br/>\n<a ng-click="unsubscribe(\'keep-all\')">Keep Tasks</a><br/>\n<a ng-click="unsubscribe(\'cancel\')">Cancel</a><br/>'
)($scope); )($scope);
$scope.popoverEl.popover('destroy').popover({ $scope.popoverEl.popover('destroy').popover({
html: true, html: true,

View File

@@ -81,12 +81,8 @@ var syncChalToUser = function(chal, user) {
tags[chal._id] = true; tags[chal._id] = true;
_.each(['habits','dailys','todos','rewards'], function(type){ _.each(['habits','dailys','todos','rewards'], function(type){
_.each(chal[type], function(task){ _.each(chal[type], function(task){
_.defaults(task, { _.defaults(task, {tags: tags, challenge:{}});
tags: tags, _.defaults(task.challenge, {id:chal._id, broken:false});
challenge: chal._id,
group: chal.group
});
if (~(i = _.findIndex(user[type], {id:task.id}))) { if (~(i = _.findIndex(user[type], {id:task.id}))) {
_.defaults(user[type][i], task); _.defaults(user[type][i], task);
} else { } else {
@@ -121,11 +117,36 @@ api.join = function(req, res){
}); });
} }
api.leave = function(req, res, next){ function unlink(user, cid, keep, tid) {
switch (keep) {
case 'keep':
delete user.tasks[tid].challenge;
break;
case 'remove':
user[user.tasks[tid].type+'s'].id(tid).remove();
break;
case 'keep-all':
_.each(user.tasks, function(t){
if (t.challenge && t.challenge.id == cid) {
delete t.challenge;
}
});
break;
case 'remove-all':
_.each(user.tasks, function(t){
if (t.challenge && t.challenge.id == cid) {
user[t.type+'s'].id(t.id).remove();
}
})
break;
}
}
api.leave = function(req, res){
var user = res.locals.user; var user = res.locals.user;
var cid = req.params.cid; var cid = req.params.cid;
// whether or not to keep challenge's tasks. strictly default to true if "false" isn't provided // whether or not to keep challenge's tasks. strictly default to true if "keep-all" isn't provided
var keep = !(/^false$/i).test(req.query.keep); var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all';
async.waterfall([ async.waterfall([
function(cb){ function(cb){
@@ -136,16 +157,7 @@ api.leave = function(req, res, next){
//User.findByIdAndUpdate(user._id, {$pull:{challenges:cid}}, cb); //User.findByIdAndUpdate(user._id, {$pull:{challenges:cid}}, cb);
var i = user.challenges.indexOf(cid) var i = user.challenges.indexOf(cid)
if (~i) user.challenges.splice(i,1); if (~i) user.challenges.splice(i,1);
unlink(user, chal._id, keep)
// Remove tasks from user
_.each(chal.tasks, function(task) {
if (keep) {
delete user[task.type+'s'].id(task.id).challenge;
delete user[task.type+'s'].id(task.id).group;
} else {
user[task.type+'s'].id(task.id).remove();
}
});
user.save(function(err){ user.save(function(err){
if (err) return cb(err); if (err) return cb(err);
cb(null, chal); cb(null, chal);
@@ -156,3 +168,16 @@ api.leave = function(req, res, next){
res.json(result); res.json(result);
}); });
} }
api.unlink = function(req, res) {
var user = res.locals.user;
var tid = req.params.id;
var cid = user.tasks[tid].challenge.id;
if (!req.query.keep)
return res.json(400, {err: 'Provide unlink method as ?keep=keep-all (keep, keep-all, remove, remove-all)'});
unlink(user, cid, req.query.keep, tid);
user.save(function(err, saved){
if (err) return res.json(500,{err:err});
res.send(200);
});
}

View File

@@ -25,7 +25,12 @@ var TaskSchema = new Schema({
completed: {type: Boolean, 'default': false}, completed: {type: Boolean, 'default': false},
priority: {type: String, 'default': '!'}, //'!!' // FIXME this should be a number or something priority: {type: String, 'default': '!'}, //'!!' // FIXME this should be a number or something
repeat: {type: Schema.Types.Mixed, 'default': {m:1, t:1, w:1, th:1, f:1, s:1, su:1} }, repeat: {type: Schema.Types.Mixed, 'default': {m:1, t:1, w:1, th:1, f:1, s:1, su:1} },
streak: {type: Number, 'default': 0} streak: {type: Number, 'default': 0},
challenge: {
id: {type: 'String', ref:'Challenge'},
broken: {type: Boolean, 'default': false}
// group: {type: 'Strign', redf: 'Group'} // if we restore this, rename `id` above to `challenge`
}
}); });
TaskSchema.methods.toJSON = function() { TaskSchema.methods.toJSON = function() {

View File

@@ -37,6 +37,7 @@ router["delete"]('/user/task/:id', auth.auth, cron, verifyTaskExists, user.delet
router.post('/user/task', auth.auth, cron, user.createTask); router.post('/user/task', auth.auth, cron, user.createTask);
router.put('/user/task/:id/sort', auth.auth, cron, verifyTaskExists, user.sortTask); router.put('/user/task/:id/sort', auth.auth, cron, verifyTaskExists, user.sortTask);
router.post('/user/clear-completed', auth.auth, cron, user.clearCompleted); router.post('/user/clear-completed', auth.auth, cron, user.clearCompleted);
router.post('/user/task/:id/unlink', auth.auth, challenges.unlink); // removing cron since they may want to remove task first
/* Items*/ /* Items*/
router.post('/user/buy/:type', auth.auth, cron, user.buy); router.post('/user/buy/:type', auth.auth, cron, user.buy);

View File

@@ -1,112 +0,0 @@
//-
NOTE: This file is a copy of /views/tasks/task.jade, make sure to keep them in sync!
We've cloned it because they differ widely enough that if(challenge)/else checks got out of
hand, and we needed separate controllers.
li(ng-repeat='task in list.tasks', class='task {{taskClasses(task)}}', data-id='{{task.id}}')
.task-meta-controls
// TODO Do we want to show participants' streaks?
//- Streak
span(ng-show='task.streak') {{task.streak}}
span(tooltip='Streak Counter')
i.icon-forward
// edit
a(ng-hide='task._editing', ng-click='toggleEdit(task)', tooltip='Edit')
i.icon-pencil(ng-hide='task._editing')
// cancel
a(ng-hide='!task._editing', ng-click='toggleEdit(task)', tooltip='Cancel')
i.icon-remove(ng-hide='!task._editing')
// delete
a(ng-click='remove(task)', tooltip='Delete')
i.icon-trash
// chart
a(ng-show='task.history', ng-click='toggleChart(task.id, task)', tooltip='Progress')
i.icon-signal
// notes
span.task-notes(ng-show='task.notes && !task._editing', popover-trigger='mouseenter', popover-placement='left', popover='{{task.notes}}', popover-title='{{task.text}}')
i.icon-comment
// left-hand side checkbox
.task-controls.task-primary
// Habits
span(ng-if='task.type=="habit"')
a.task-action-btn(ng-if='task.up') +
a.task-action-btn(ng-if='task.down') -
// Rewards
span(ng-show='task.type=="reward"')
a.money.btn-buy
span.reward-cost {{task.value}}
span.shop_gold
// Daily & Todos
span.task-checker.action-yesno(ng-if='task.type=="daily" || task.type=="todo"')
input.visuallyhidden.focusable(id='box-{{task.id}}-static', type='checkbox', ng-model='task.completed')
label(for='box-{{task.id}}-static')
// main content
p.task-text
| {{task.text}}
// edit/options dialog
.task-options(ng-show='task._editing')
form(ng-submit='saveTask(task)')
// text & notes
fieldset.option-group
label.option-title Text
input.option-content(type='text', ng-model='task.text', required)
label.option-title Extra Notes
textarea.option-content(rows='3', ng-model='task.notes')
// if Habit, plus/minus command options
fieldset.option-group(ng-if='task.type=="habit"')
legend.option-title Direction/Actions
span.task-checker.action-plusminus.select-toggle
input.visuallyhidden.focusable(id='{{task.id}}-option-plus', type='checkbox', ng-model='task.up')
label(for='{{task.id}}-option-plus')
span.task-checker.action-plusminus.select-toggle
input.visuallyhidden.focusable(id='{{task.id}}-option-minus', type='checkbox', ng-model='task.down')
label(for='{{task.id}}-option-minus')
// if Daily, calendar
fieldset(ng-if='task.type=="daily"', class="option-group")
legend.option-title Repeat
.task-controls.tile-group.repeat-days
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
button.task-action-btn.tile(ng-class='{active: task.repeat.su}', type='button', data-day='su', ng-click='task.repeat.su = !task.repeat.su') Su
button.task-action-btn.tile(ng-class='{active: task.repeat.m}', type='button', data-day='m', ng-click='task.repeat.m = !task.repeat.m') M
button.task-action-btn.tile(ng-class='{active: task.repeat.t}', type='button', data-day='t', ng-click='task.repeat.t = !task.repeat.t') T
button.task-action-btn.tile(ng-class='{active: task.repeat.w}', type='button', data-day='w', ng-click='task.repeat.w = !task.repeat.w') W
button.task-action-btn.tile(ng-class='{active: task.repeat.th}', type='button', data-day='th', ng-click='task.repeat.th = !task.repeat.th') Th
button.task-action-btn.tile(ng-class='{active: task.repeat.f}', type='button', data-day='f', ng-click='task.repeat.f = !task.repeat.f') F
button.task-action-btn.tile(ng-class='{active: task.repeat.s}', type='button', data-day='s', ng-click='task.repeat.s = !task.repeat.s') S
// if Reward, pricing
fieldset.option-group.option-short(ng-if='task.type=="reward"')
legend.option-title Price
input.option-content(type='number', size='16', min='0', step="any", ng-model='task.value')
.money.input-suffix
span.shop_gold
// if Todos, the due date
fieldset.option-group(ng-if='task.type=="todo"')
legend.option-title Due Date
input.option-content.datepicker(type='text', ng-model='task.date', data-date-format='mm/dd/yyyy')
// Advanced Options
span(ng-if='task.type!="reward"')
p.option-title.mega(ng-click='task._advanced = !task._advanced') Advanced Options
fieldset.option-group.advanced-option(ng-class="{visuallyhidden: !task._advanced}")
legend.option-title
a.priority-multiplier-help(href='https://trello.com/card/priority-multiplier/50e5d3684fe3a7266b0036d6/17', target='_blank', popover-title='How difficult is this task?', popover-trigger='mouseenter', popover="This multiplies its point value. Use sparingly, rely instead on our organic value-adjustment algorithms. But some tasks are grossly more valuable (Write Thesis vs Floss Teeth). Click for more info.")
i.icon-question-sign
| Difficulty
.task-controls.tile-group.priority-multiplier(data-id='{{task.id}}')
button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!" || !task.priority}', ng-click='task.priority="!"') Easy
button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!!"}', ng-click='task.priority="!!"') Medium
button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!!!"}', ng-click='task.priority="!!!"') Hard
button.task-action-btn.tile.spacious(type='submit') Save & Close
div(class='{{task.id}}-chart', ng-show='charts[task.id]')

View File

@@ -1,21 +0,0 @@
.grid
.module(ng-repeat='list in taskLists', ng-class='{"rewards-module": list.type==="reward"}')
.task-column(class='{{list.type}}s')
// Removed graph icon. Restore here from tasks/index.jade if needed
// Header
h2.task-column_title {{list.header}}
// Removed graph. Restore here from tasks/index.jade if needed
// Add New
form.addtask-form.form-inline.new-task-form(name='new{{list.type}}form', ng-if='challenge.leader == user._id', ng-submit='addTask(list)')
span.addtask-field
input(type='text', ng-model='list.newTask', placeholder='{{list.placeHolder}}', required)
input.addtask-btn(type='submit', value='', ng-disabled='new{{list.type}}form.$invalid')
hr
// Actual List
ul(class='{{list.type}}s', ng-show='list.tasks.length > 0', habitrpg-sortable)
include ./task

View File

@@ -29,7 +29,7 @@ script(id='templates/habitrpg-options.html', type="text/ng-template")
a(data-toggle='tab',data-target='#achievements-tab') a(data-toggle='tab',data-target='#achievements-tab')
i.icon-certificate i.icon-certificate
| Achievements | Achievements
//-li li
a(data-toggle='tab',data-target='#challenges-tab') a(data-toggle='tab',data-target='#challenges-tab')
i.icon-bullhorn i.icon-bullhorn
| Challenges | Challenges
@@ -58,7 +58,7 @@ script(id='templates/habitrpg-options.html', type="text/ng-template")
include ../shared/profiles/achievements include ../shared/profiles/achievements
.tab-pane#challenges-tab .tab-pane#challenges-tab
include ./challenges/index include ./challenges
.tab-pane#settings-tab .tab-pane#settings-tab
include ./settings include ./settings

View File

@@ -17,20 +17,15 @@ li(ng-repeat='task in list.tasks', class='task {{taskClasses(task, user.filters,
// cancel // cancel
a(ng-hide='!task._editing', ng-click='editTask(task)', tooltip='Cancel') a(ng-hide='!task._editing', ng-click='editTask(task)', tooltip='Cancel')
i.icon-remove(ng-hide='!task._editing') i.icon-remove(ng-hide='!task._editing')
//- challenges //challenges
//- {{#if :task.challenge}} span(ng-if='task.challenge.id')
//- {{#if brokenChallengeLink(:task)}} span(ng-if='task.challenge.broken')
//- <i class='icon-bullhorn' style='background-color:red;' x-bind=click:tasks.toggleTaskEdit rel=tooltip title="Broken Challenge Link"></i> i.icon-bullhorn(style='background-color:red;', ng-click='task._edit=true', tooltip="Broken Challenge Link")
//- {{else}} span(ng-if='!task.challenge.broken')
//- <i class='icon-bullhorn' rel=tooltip title="Challenge Task"></i> i.icon-bullhorn(tooltip="Challenge Task")
//- {{/}}
//- {{else}}
//- <!-- delete -->
//- <a x-bind="click:tasks.del" rel=tooltip title="Delete"><i class="icon-trash"></i></a>
//- {{/}}
// delete // delete
a(ng-click='removeTask(list.tasks, $index)', tooltip='Delete') a(ng-if='!task.challenge.id', ng-click='removeTask(list.tasks, $index)', tooltip='Delete')
i.icon-trash i.icon-trash
// chart // chart
a(ng-show='task.history', ng-click='toggleChart(task.id, task)', tooltip='Progress') a(ng-show='task.history', ng-click='toggleChart(task.id, task)', tooltip='Progress')
@@ -59,48 +54,43 @@ li(ng-repeat='task in list.tasks', class='task {{taskClasses(task, user.filters,
label(for='box-{{task.id}}') label(for='box-{{task.id}}')
// main content // main content
p.task-text p.task-text
// {{#if taskInChallenge(task)}}
// {{taskAttrFromChallenge(task,'text')}}
// {{else}}
| {{task.text}} | {{task.text}}
// {{/}}
// edit/options dialog // edit/options dialog
.task-options(ng-show='task._editing') .task-options(ng-show='task._editing')
//
// {#if brokenChallengeLink(:task)} // Broken Challenge
// <div class='well'> .well(ng-if='task.challenge.broken')
// {{#if groups[:task.group.id].challenges[:task.challenge]}} div(ng-if='task.challenge.broken==1')
// <p>Broken Challenge Link: this task was part of a challenge, but has been removed from it. What would you like to do?</p> p Broken Challenge Link: this task was part of a challenge, but has been removed from it. What would you like to do?
// <p> p
// <a x-bind="click:challenges.taskUnsubscribe" data-action="keep" data-tid="{{:task.id}}">Keep It</a>&nbsp;|&nbsp; a(ng-click="unlink(task, 'keep')") Keep It
// <a x-bind="click:challenges.taskUnsubscribe" data-action="remove" data-tid="{{:task.id}}">Remove It</a> | &nbsp;|&nbsp;
// </p> a(ng-click="remove(list, $index)") Remove It
// {{else}} div(ng-if='task.challenge.broken==2')
// <p>Broken Challenge Link: this task was part of a challenge, but the challenge (or group) has been deleted. What would you like to do with these poor lil' orphans?</p> p Broken Challenge Link: this task was part of a challenge, but the challenge (or group) has been deleted. What to do with the orphan tasks?
// <p> p
// <a x-bind="click:challenges.taskUnsubscribe" data-action="keep-all" data-tid="{{:task.id}}">Keep Them</a>&nbsp;|&nbsp; a(ng-click="unlink(task 'keep-all')") Keep Them
// <a x-bind="click:challenges.taskUnsubscribe" data-action="remove-all" data-tid="{{:task.id}}">Remove Them</a> | &nbsp;|&nbsp;
// </p> a(ng-click="unlink(task, 'remove-all')") Remove Them
// {{/}} div(ng-if='task.challenge.broken==3')
// </div> p Broken Challenge Link: this task was part of a challenge, but you have unsubscribed from the challenge. What to do with the orphan tasks?
// {/} p
a(ng-click="unlink(task, 'keep-all')") Keep Them
| &nbsp;|&nbsp;
a(ng-click="unlink(task, 'remove-all')") Remove Them
form(ng-submit='saveTask(task)') form(ng-submit='saveTask(task)')
// text & notes // text & notes
fieldset.option-group fieldset.option-group
// {{#unless taskInChallenge(task)}}
label.option-title Text label.option-title Text
input.option-content(type='text', ng-model='task.text', required) input.option-content(type='text', ng-model='task.text', required, ng-disabled='task.challenge.id')
// {{/}}
label.option-title Extra Notes label.option-title Extra Notes
// {{#if taskInChallenge(task)}} textarea.option-content(rows='3', ng-model='task.notes', ng-disabled='task.challenge.id')
// <textarea class="option-content" rows=3 disabled>{{taskAttrFromChallenge(task,'notes')}}</textarea>
// {{else}}
textarea.option-content(rows='3', ng-model='task.notes')
// {{/}}
// if Habit, plus/minus command options // if Habit, plus/minus command options
// {{#unless taskInChallenge(task)}} fieldset.option-group(ng-if='task.type=="habit" && !task.challenge.id')
fieldset.option-group(ng-if='task.type=="habit"')
legend.option-title Direction/Actions legend.option-title Direction/Actions
span.task-checker.action-plusminus.select-toggle span.task-checker.action-plusminus.select-toggle
input.visuallyhidden.focusable(id='{{task.id}}-option-plus', type='checkbox', ng-model='task.up') input.visuallyhidden.focusable(id='{{task.id}}-option-plus', type='checkbox', ng-model='task.up')
@@ -108,9 +98,10 @@ li(ng-repeat='task in list.tasks', class='task {{taskClasses(task, user.filters,
span.task-checker.action-plusminus.select-toggle span.task-checker.action-plusminus.select-toggle
input.visuallyhidden.focusable(id='{{task.id}}-option-minus', type='checkbox', ng-model='task.down') input.visuallyhidden.focusable(id='{{task.id}}-option-minus', type='checkbox', ng-model='task.down')
label(for='{{task.id}}-option-minus') label(for='{{task.id}}-option-minus')
// {{/}}
// if Daily, calendar // if Daily, calendar
fieldset(ng-if='task.type=="daily"', class="option-group") // FIXME display, but disable for challenge tasks
fieldset(ng-if='task.type=="daily" && !task.challenge.id', class="option-group")
legend.option-title Repeat legend.option-title Repeat
.task-controls.tile-group.repeat-days .task-controls.tile-group.repeat-days
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding // note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
@@ -121,42 +112,38 @@ li(ng-repeat='task in list.tasks', class='task {{taskClasses(task, user.filters,
button.task-action-btn.tile(ng-class='{active: task.repeat.th}', type='button', data-day='th', ng-click='task.repeat.th = !task.repeat.th') Th button.task-action-btn.tile(ng-class='{active: task.repeat.th}', type='button', data-day='th', ng-click='task.repeat.th = !task.repeat.th') Th
button.task-action-btn.tile(ng-class='{active: task.repeat.f}', type='button', data-day='f', ng-click='task.repeat.f = !task.repeat.f') F button.task-action-btn.tile(ng-class='{active: task.repeat.f}', type='button', data-day='f', ng-click='task.repeat.f = !task.repeat.f') F
button.task-action-btn.tile(ng-class='{active: task.repeat.s}', type='button', data-day='s', ng-click='task.repeat.s = !task.repeat.s') S button.task-action-btn.tile(ng-class='{active: task.repeat.s}', type='button', data-day='s', ng-click='task.repeat.s = !task.repeat.s') S
// if Reward, pricing // if Reward, pricing
fieldset.option-group.option-short(ng-if='task.type=="reward"') fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')
legend.option-title Price legend.option-title Price
input.option-content(type='number', size='16', min='0', step="any", ng-model='task.value') input.option-content(type='number', size='16', min='0', step="any", ng-model='task.value')
.money.input-suffix .money.input-suffix
span.shop_gold span.shop_gold
// if Todos, the due date // if Todos, the due date
fieldset.option-group(ng-if='task.type=="todo"') fieldset.option-group(ng-if='task.type=="todo" && !task.challenge.id')
legend.option-title Due Date legend.option-title Due Date
input.option-content.datepicker(type='text', data-date-format='mm/dd/yyyy', ng-model='task.date') input.option-content.datepicker(type='text', data-date-format='mm/dd/yyyy', ng-model='task.date')
fieldset.option-group fieldset.option-group(ng-if='!task.challenge.id')
legend.option-title Tags legend.option-title Tags
label.checkbox(ng-repeat='tag in user.tags') label.checkbox(ng-repeat='tag in user.tags')
input(type='checkbox', ng-model='task.tags[tag.id]') input(type='checkbox', ng-model='task.tags[tag.id]')
| {{tag.name}} | {{tag.name}}
// Advanced Options // Advanced Options
span(ng-if='task.type!="reward"') span(ng-if='task.type!="reward"', ng-if='!task.challenge.id')
p.option-title.mega(ng-click='task._advanced = !task._advanced') Advanced Options p.option-title.mega(ng-click='task._advanced = !task._advanced') Advanced Options
fieldset.option-group.advanced-option(ng-class="{visuallyhidden: !task._advanced}") fieldset.option-group.advanced-option(ng-class="{visuallyhidden: !task._advanced}")
legend.option-title legend.option-title
a.priority-multiplier-help(href='https://trello.com/card/priority-multiplier/50e5d3684fe3a7266b0036d6/17', target='_blank', popover-title='How difficult is this task?', popover-trigger='mouseenter', popover="This multiplies its point value. Use sparingly, rely instead on our organic value-adjustment algorithms. But some tasks are grossly more valuable (Write Thesis vs Floss Teeth). Click for more info.") a.priority-multiplier-help(href='https://trello.com/card/priority-multiplier/50e5d3684fe3a7266b0036d6/17', target='_blank', popover-title='How difficult is this task?', popover-trigger='mouseenter', popover="This multiplies its point value. Use sparingly, rely instead on our organic value-adjustment algorithms. But some tasks are grossly more valuable (Write Thesis vs Floss Teeth). Click for more info.")
i.icon-question-sign i.icon-question-sign
| Difficulty | Difficulty
// {{#if taskInChallenge(task)}}
// <button disabled type="button" class="task-action-btn tile active">
// {{taskAttrFromChallenge(task,'priority')}}
// </button>
// {{else}}
.task-controls.tile-group.priority-multiplier(data-id='{{task.id}}') .task-controls.tile-group.priority-multiplier(data-id='{{task.id}}')
button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!" || !task.priority}', ng-click='task.priority="!"') Easy button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!" || !task.priority}', ng-click='task.priority="!"') Easy
button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!!"}', ng-click='task.priority="!!"') Medium button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!!"}', ng-click='task.priority="!!"') Medium
button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!!!"}', ng-click='task.priority="!!!"') Hard button.task-action-btn.tile(type='button', ng-class='{active: task.priority=="!!!"}', ng-click='task.priority="!!!"') Hard
// {{/}} span(ng-if='task.type=="daily" && !task.challenge.id')
span(ng-if='task.type=="daily"')
legend.option-title Restore Streak legend.option-title Restore Streak
input.option-content(type='number', ng-model='task.streak') input.option-content(type='number', ng-model='task.streak')
button.task-action-btn.tile.spacious(type='submit') Save & Close button.task-action-btn.tile.spacious(type='submit') Save & Close