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)}
- //
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 - //
- // {{/}} - //