diff --git a/public/js/controllers/challengesCtrl.js b/public/js/controllers/challengesCtrl.js index 4a882dd2f0..f43d00558e 100644 --- a/public/js/controllers/challengesCtrl.js +++ b/public/js/controllers/challengesCtrl.js @@ -168,52 +168,13 @@ habitrpg.controller("ChallengesCtrl", ['$scope', '$rootScope', 'User', 'Challeng -------------------------- */ - $scope.taskUnsubscribe = function(e, el) { - /* - since the challenge was deleted, we don't have its data to unsubscribe from - but we have the vestiges on the task - FIXME this is a really dumb way of doing this - */ - - 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.unlink = function(task, keep) { + // TODO move this to userServices, turn userSerivces.user into ng-resource + $http.post(API_URL + '/api/v1/user/task/' + task.id + '/unlink', {keep:keep}) + .success(function(){ + debugger + User.log({}); + }); }; $scope.unsubscribe = function(keep) { @@ -228,7 +189,7 @@ habitrpg.controller("ChallengesCtrl", ['$scope', '$rootScope', 'User', 'Challeng $scope.selectedChal = chal; $scope.popoverEl = $($event.target); var html = $compile( - 'Remove Tasks
\nKeep Tasks
\nCancel
' + 'Remove Tasks
\nKeep Tasks
\nCancel
' )($scope); $scope.popoverEl.popover('destroy').popover({ html: true, diff --git a/src/controllers/challenges.js b/src/controllers/challenges.js index 9ef575dfc4..7b4467041e 100644 --- a/src/controllers/challenges.js +++ b/src/controllers/challenges.js @@ -81,12 +81,8 @@ var syncChalToUser = function(chal, user) { tags[chal._id] = true; _.each(['habits','dailys','todos','rewards'], function(type){ _.each(chal[type], function(task){ - _.defaults(task, { - tags: tags, - challenge: chal._id, - group: chal.group - }); - + _.defaults(task, {tags: tags, challenge:{}}); + _.defaults(task.challenge, {id:chal._id, broken:false}); if (~(i = _.findIndex(user[type], {id:task.id}))) { _.defaults(user[type][i], task); } 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 cid = req.params.cid; - // whether or not to keep challenge's tasks. strictly default to true if "false" isn't provided - var keep = !(/^false$/i).test(req.query.keep); + // whether or not to keep challenge's tasks. strictly default to true if "keep-all" isn't provided + var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all'; async.waterfall([ function(cb){ @@ -136,16 +157,7 @@ api.leave = function(req, res, next){ //User.findByIdAndUpdate(user._id, {$pull:{challenges:cid}}, cb); var i = user.challenges.indexOf(cid) if (~i) user.challenges.splice(i,1); - - // 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(); - } - }); + unlink(user, chal._id, keep) user.save(function(err){ if (err) return cb(err); cb(null, chal); @@ -155,4 +167,17 @@ api.leave = function(req, res, next){ if(err) return res.json(500,{err:err}); 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); + }); } \ No newline at end of file diff --git a/src/models/task.js b/src/models/task.js index 8abdd9302e..cbaecb6491 100644 --- a/src/models/task.js +++ b/src/models/task.js @@ -25,7 +25,12 @@ var TaskSchema = new Schema({ completed: {type: Boolean, 'default': false}, 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} }, - 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() { diff --git a/src/routes/api.js b/src/routes/api.js index 7983757362..16899b6b8a 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -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.put('/user/task/:id/sort', auth.auth, cron, verifyTaskExists, user.sortTask); 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*/ router.post('/user/buy/:type', auth.auth, cron, user.buy); diff --git a/views/options/challenges/index.jade b/views/options/challenges.jade similarity index 100% rename from views/options/challenges/index.jade rename to views/options/challenges.jade diff --git a/views/options/challenges/task.jade b/views/options/challenges/task.jade deleted file mode 100644 index 8f89fbcbb7..0000000000 --- a/views/options/challenges/task.jade +++ /dev/null @@ -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]') diff --git a/views/options/challenges/tasks.jade b/views/options/challenges/tasks.jade deleted file mode 100644 index 0b094e5645..0000000000 --- a/views/options/challenges/tasks.jade +++ /dev/null @@ -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 \ No newline at end of file diff --git a/views/options/index.jade b/views/options/index.jade index c3ea045c1f..75d917912f 100644 --- a/views/options/index.jade +++ b/views/options/index.jade @@ -29,7 +29,7 @@ script(id='templates/habitrpg-options.html', type="text/ng-template") a(data-toggle='tab',data-target='#achievements-tab') i.icon-certificate | Achievements - //-li + li a(data-toggle='tab',data-target='#challenges-tab') i.icon-bullhorn | Challenges @@ -58,7 +58,7 @@ script(id='templates/habitrpg-options.html', type="text/ng-template") include ../shared/profiles/achievements .tab-pane#challenges-tab - include ./challenges/index + include ./challenges .tab-pane#settings-tab include ./settings \ No newline at end of file diff --git a/views/shared/tasks/task.jade b/views/shared/tasks/task.jade index aafb4d6bc6..d048e6a1c3 100644 --- a/views/shared/tasks/task.jade +++ b/views/shared/tasks/task.jade @@ -17,20 +17,15 @@ li(ng-repeat='task in list.tasks', class='task {{taskClasses(task, user.filters, // cancel a(ng-hide='!task._editing', ng-click='editTask(task)', tooltip='Cancel') i.icon-remove(ng-hide='!task._editing') - //- challenges - //- {{#if :task.challenge}} - //- {{#if brokenChallengeLink(:task)}} - //- - //- {{else}} - //- - //- {{/}} - //- {{else}} - //- - //- - //- {{/}} + //challenges + span(ng-if='task.challenge.id') + span(ng-if='task.challenge.broken') + i.icon-bullhorn(style='background-color:red;', ng-click='task._edit=true', tooltip="Broken Challenge Link") + span(ng-if='!task.challenge.broken') + i.icon-bullhorn(tooltip="Challenge Task") // 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 // chart 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}}') // main content p.task-text - // {{#if taskInChallenge(task)}} - // {{taskAttrFromChallenge(task,'text')}} - // {{else}} | {{task.text}} - // {{/}} // edit/options dialog .task-options(ng-show='task._editing') - // - // {#if brokenChallengeLink(:task)} - //
- // {{#if groups[:task.group.id].challenges[:task.challenge]}} - //

Broken Challenge Link: this task was part of a challenge, but has been removed from it. What would you like to do?

- //

- // Keep It |  - // Remove It - //

- // {{else}} - //

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?

- //

- // Keep Them |  - // Remove Them - //

- // {{/}} - //
- // {/} + + // Broken Challenge + .well(ng-if='task.challenge.broken') + 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 + a(ng-click="unlink(task, 'keep')") Keep It + |  |  + a(ng-click="remove(list, $index)") Remove It + 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 to do with the orphan tasks? + p + a(ng-click="unlink(task 'keep-all')") Keep Them + |  |  + a(ng-click="unlink(task, 'remove-all')") Remove Them + div(ng-if='task.challenge.broken==3') + 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 + |  |  + a(ng-click="unlink(task, 'remove-all')") Remove Them + form(ng-submit='saveTask(task)') // text & notes fieldset.option-group - // {{#unless taskInChallenge(task)}} 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 - // {{#if taskInChallenge(task)}} - // - // {{else}} - textarea.option-content(rows='3', ng-model='task.notes') - // {{/}} + textarea.option-content(rows='3', ng-model='task.notes', ng-disabled='task.challenge.id') + // if Habit, plus/minus command options - // {{#unless taskInChallenge(task)}} - fieldset.option-group(ng-if='task.type=="habit"') + fieldset.option-group(ng-if='task.type=="habit" && !task.challenge.id') 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') @@ -108,55 +98,52 @@ li(ng-repeat='task in list.tasks', class='task {{taskClasses(task, user.filters, 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") + // FIXME display, but disable for challenge tasks + fieldset(ng-if='task.type=="daily" && !task.challenge.id', 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 + 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"') + fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id') 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"') + fieldset.option-group(ng-if='task.type=="todo" && !task.challenge.id') legend.option-title Due 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 label.checkbox(ng-repeat='tag in user.tags') input(type='checkbox', ng-model='task.tags[tag.id]') | {{tag.name}} // 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 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 - // {{#if taskInChallenge(task)}} - // - // {{else}} .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 - // {{/}} - span(ng-if='task.type=="daily"') + span(ng-if='task.type=="daily" && !task.challenge.id') legend.option-title Restore Streak input.option-content(type='number', ng-model='task.streak') button.task-action-btn.tile.spacious(type='submit') Save & Close