diff --git a/public/js/app.js b/public/js/app.js index 3ae6e6e098..73936bae9a 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1,7 +1,7 @@ "use strict"; window.habitrpg = angular.module('habitrpg', - ['ngRoute', 'ngResource', 'ngSanitize', 'userServices', 'groupServices', 'memberServices', 'sharedServices', 'authServices', 'notificationServices', 'guideServices', 'ui.bootstrap', 'ui.keypress']) + ['ngRoute', 'ngResource', 'ngSanitize', 'userServices', 'groupServices', 'memberServices', 'challengeServices', 'sharedServices', 'authServices', 'notificationServices', 'guideServices', 'ui.bootstrap', 'ui.keypress']) .constant("API_URL", "") .constant("STORAGE_USER_ID", 'habitrpg-user') @@ -12,8 +12,8 @@ window.habitrpg = angular.module('habitrpg', function($routeProvider, $httpProvider, STORAGE_SETTINGS_ID) { $routeProvider //.when('/login', {templateUrl: 'views/login.html'}) - .when('/tasks', {templateUrl: 'partials/tasks'}) - .when('/options', {templateUrl: 'partials/options'}) + .when('/tasks', {templateUrl: 'templates/habitrpg-main.html'}) + .when('/options', {templateUrl: 'templates/habitrpg-options.html'}) .otherwise({redirectTo: '/tasks'}); diff --git a/public/js/controllers/filtersCtrl.js b/public/js/controllers/filtersCtrl.js index 86444d219c..3839074a6c 100644 --- a/public/js/controllers/filtersCtrl.js +++ b/public/js/controllers/filtersCtrl.js @@ -34,7 +34,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'API_URL', ' delete user.filters[tag.id]; user.tags.splice($index,1); // remove tag from all tasks - _.each(user.tasks, function(task) { + _.each(user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards), function(task) { delete task.tags[tag.id]; }); User.log({op:'delTag',data:{'tag':tag.id}}) diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index 0726686180..1ad39e2285 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -12,6 +12,11 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ $rootScope.settings = User.settings; $rootScope.flash = {errors: [], warnings: []}; + // indexOf helper + $scope.indexOf = function(haystack, needle){ + return ~haystack.indexOf(needle); + } + $scope.safeApply = function(fn) { var phase = this.$root.$$phase; if(phase == '$apply' || phase == '$digest') { diff --git a/public/js/controllers/taskDetailsCtrl.js b/public/js/controllers/taskDetailsCtrl.js deleted file mode 100644 index 1d67b72277..0000000000 --- a/public/js/controllers/taskDetailsCtrl.js +++ /dev/null @@ -1,53 +0,0 @@ -"use strict"; - -habitrpg.controller("TaskDetailsCtrl", ['$scope', '$rootScope', '$location', 'User', - function($scope, $rootScope, $location, User) { - $scope.save = function(task) { - var log, setVal; - setVal = function(k, v) { - var op; - if (typeof v !== "undefined") { - op = { - op: "set", - data: {} - }; - op.data["tasks." + task.id + "." + k] = v; - return log.push(op); - } - }; - log = []; - setVal("text", task.text); - setVal("notes", task.notes); - setVal("priority", task.priority); - setVal("tags", task.tags); - if (task.type === "habit") { - setVal("up", task.up); - setVal("down", task.down); - } else if (task.type === "daily") { - setVal("repeat", task.repeat); - // TODO we'll remove this once rewrite's running for a while. This was a patch for derby issues - setVal("streak", task.streak); - - } else if (task.type === "todo") { - setVal("date", task.date); - } else { - if (task.type === "reward") { - setVal("value", task.value); - } - } - User.log(log); - task._editing = false; - }; - $scope.cancel = function() { - /* reset $scope.task to $scope.originalTask - */ - - var key; - for (key in $scope.task) { - $scope.task[key] = $scope.originalTask[key]; - } - $scope.originalTask = null; - $scope.editedTask = null; - $scope.editing = false; - }; -}]); diff --git a/public/js/controllers/tasksCtrl.js b/public/js/controllers/tasksCtrl.js index 0c48e2758e..a33919ce0c 100644 --- a/public/js/controllers/tasksCtrl.js +++ b/public/js/controllers/tasksCtrl.js @@ -2,35 +2,6 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', 'Algos', 'Helpers', 'Notification', function($scope, $rootScope, $location, User, Algos, Helpers, Notification) { - /*FIXME - */ - $scope.taskLists = [ - { - header: 'Habits', - type: 'habit', - placeHolder: 'New Habit', - main: true, - editable: true - }, { - header: 'Dailies', - type: 'daily', - placeHolder: 'New Daily', - main: true, - editable: true - }, { - header: 'Todos', - type: 'todo', - placeHolder: 'New Todo', - main: true, - editable: true - }, { - header: 'Rewards', - type: 'reward', - placeHolder: 'New Reward', - main: true, - editable: true - } - ]; $scope.score = function(task, direction) { if (task.type === "reward" && User.user.stats.gp < task.value){ return Notification.text('Not enough GP.'); @@ -42,18 +13,20 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', ' $scope.addTask = function(list) { var task = window.habitrpgShared.helpers.taskDefaults({text: list.newTask, type: list.type}, User.user.filters); - User.user[list.type + "s"].unshift(task); - // $scope.showedTasks.unshift newTask # FIXME what's thiss? + list.tasks.unshift(task); User.log({op: "addTask", data: task}); delete list.newTask; }; - /*Add the new task to the actions log - */ + /** + * Add the new task to the actions log + */ $scope.clearDoneTodos = function() {}; + + /** + * This is calculated post-change, so task.completed=true if they just checked it + */ $scope.changeCheck = function(task) { - /* This is calculated post-change, so task.completed=true if they just checked it - */ if (task.completed) { $scope.score(task, "up"); } else { @@ -66,27 +39,59 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', ' // uhoh! our first name conflict with habitrpg-shared/helpers, we gotta resovle that soon. $rootScope.clickRevive = function() { window.habitrpgShared.algos.revive(User.user); - User.log({ - op: "revive" - }); + User.log({ op: "revive" }); }; - $scope.toggleEdit = function(task){ - task._editing = !task._editing; - if($rootScope.charts[task.id]) $rootScope.charts[task.id] = false; + $scope.removeTask = function(list, $index) { + if (!confirm("Are you sure you want to delete this task?")) return; + User.log({ op: "delTask", data: list[$index] }); + list.splice($index, 1); }; - $scope.remove = function(task) { - var tasks; - if (confirm("Are you sure you want to delete this task?") !== true) { - return; + $scope.saveTask = function(task) { + var setVal = function(k, v) { + var op; + if (typeof v !== "undefined") { + op = { op: "set", data: {} }; + op.data["tasks." + task.id + "." + k] = v; + return log.push(op); + } + }; + var log = []; + setVal("text", task.text); + setVal("notes", task.notes); + setVal("priority", task.priority); + setVal("tags", task.tags); + if (task.type === "habit") { + setVal("up", task.up); + setVal("down", task.down); + } else if (task.type === "daily") { + setVal("repeat", task.repeat); + // TODO we'll remove this once rewrite's running for a while. This was a patch for derby issues + setVal("streak", task.streak); + + } else if (task.type === "todo") { + setVal("date", task.date); + } else { + if (task.type === "reward") { + setVal("value", task.value); + } } - tasks = User.user[task.type + "s"]; - User.log({ - op: "delTask", - data: task - }); - tasks.splice(tasks.indexOf(task), 1); + User.log(log); + task._editing = false; + }; + + /** + * Reset $scope.task to $scope.originalTask + */ + $scope.cancel = function() { + var key; + for (key in $scope.task) { + $scope.task[key] = $scope.originalTask[key]; + } + $scope.originalTask = null; + $scope.editedTask = null; + $scope.editing = false; }; /* @@ -123,4 +128,15 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', ' User.log({op: 'clear-completed'}); } + /** + * See conversation on http://productforums.google.com/forum/#!topic/adsense/WYkC_VzKwbA, + * Adsense is very sensitive. It must be called once-and-only-once for every , else things break. + * Additionally, angular won't run javascript embedded into a script template, so we can't copy/paste + * the html provided by adsense - we need to run this function post-link + */ + $scope.initAds = function(){ + $.getScript('//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js'); + (window.adsbygoogle = window.adsbygoogle || []).push({}); + } + }]); diff --git a/public/js/directives/directives.js b/public/js/directives/directives.js index 3a1f2f302d..94ebc33417 100644 --- a/public/js/directives/directives.js +++ b/public/js/directives/directives.js @@ -109,3 +109,53 @@ habitrpg.directive('habitrpgSortable', ['User', function(User) { }; }); })() + +habitrpg + .directive('habitrpgTasks', ['$rootScope', 'User', function($rootScope, User) { + return { + restrict: 'EA', + templateUrl: 'templates/habitrpg-tasks.html', + //transclude: true, + //scope: { + // main: '@', // true if it's the user's main list + // obj: '=' + //}, + controller: function($scope, $rootScope){ + $scope.editTask = function(task){ + task._editing = !task._editing; + if($rootScope.charts[task.id]) $rootScope.charts[task.id] = false; + }; + }, + link: function(scope, element, attrs) { + scope.obj = scope[attrs.obj]; + scope.main = attrs.main; + + + scope.lists = [ + { + header: 'Habits', + type: 'habit', + placeHolder: 'New Habit', + tasks: scope.obj.habits + }, { + header: 'Dailies', + type: 'daily', + placeHolder: 'New Daily', + tasks: scope.obj.dailys + }, { + header: 'Todos', + type: 'todo', + placeHolder: 'New Todo', + tasks: scope.obj.todos + }, { + header: 'Rewards', + type: 'reward', + placeHolder: 'New Reward', + tasks: scope.obj.rewards + } + ]; + scope.editable = true; + } + } + }]); + diff --git a/public/js/services/userServices.js b/public/js/services/userServices.js index e25cc89a5c..11131a4bed 100644 --- a/public/js/services/userServices.js +++ b/public/js/services/userServices.js @@ -58,7 +58,6 @@ angular.module('userServices', []). $http.post(API_URL + '/api/v1/user/batch-update', sent, {params: {data:+new Date, _v:user._v}}) .success(function (data, status, heacreatingders, config) { - data.tasks = _.toArray(data.tasks); //make sure there are no pending actions to sync. If there are any it is not safe to apply model from server as we may overwrite user data. if (!queue.length) { //we can't do user=data as it will not update user references in all other angular controllers. diff --git a/src/controllers/user.js b/src/controllers/user.js index 7a3186d73d..54b92b20c3 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -55,98 +55,28 @@ api.marketBuy = function(req, res, next){ --------------- */ - -/* -// FIXME put this in helpers, so mobile & web can us it too -// FIXME actually, move to mongoose -*/ - - -function taskSanitizeAndDefaults(task) { - var _ref; - if (task.id == null) { - task.id = helpers.uuid(); - } - task.value = ~~task.value; - if (task.type == null) { - task.type = 'habit'; - } - if (_.isString(task.text)) { - task.text = sanitize(task.text).xss(); - } - if (_.isString(task.text)) { - task.notes = sanitize(task.notes).xss(); - } - if (task.type === 'habit') { - if (!_.isBoolean(task.up)) { - task.up = true; - } - if (!_.isBoolean(task.down)) { - task.down = true; - } - } - if ((_ref = task.type) === 'daily' || _ref === 'todo') { - if (!_.isBoolean(task.completed)) { - task.completed = false; - } - } - if (task.type === 'daily') { - if (task.repeat == null) { - task.repeat = { - m: true, - t: true, - w: true, - th: true, - f: true, - s: true, - su: true - }; - } - } - return task; -}; - /* Validate task */ - - api.verifyTaskExists = function(req, res, next) { - /* If we're updating, get the task from the user*/ - - var task; - task = res.locals.user.tasks[req.params.id]; - if (_.isEmpty(task)) { - return res.json(400, { - err: "No task found." - }); - } + // If we're updating, get the task from the user + var task = res.locals.user.tasks[req.params.id]; + if (_.isEmpty(task)) return res.json(400, {err: "No task found."}); res.locals.task = task; return next(); }; -function addTask(user, task) { - taskSanitizeAndDefaults(task); - user.tasks[task.id] = task; - user["" + task.type + "Ids"].unshift(task.id); - return task; -}; - -/* Override current user.task with incoming values, then sanitize all values*/ - - -function updateTask(user, id, incomingTask) { - return user.tasks[id] = taskSanitizeAndDefaults(_.defaults(incomingTask, user.tasks[id])); -}; - function deleteTask(user, task) { - var i, ids; - delete user.tasks[task.id]; - if ((ids = user["" + task.type + "Ids"]) && ~(i = ids.indexOf(task.id))) { - return ids.splice(i, 1); - } + user[task.type+'s'].id(task.id).remove(); }; +function addTask(user, task) { + var type = task.type || 'habit' + user[type+'s'].unshift(task); + // FIXME will likely have to use taskSchema instead, so we can populate the defaults, add the _id, and return the added task + return user[task.type+'s'][0]; +} + /* API Routes --------------- @@ -158,52 +88,42 @@ function deleteTask(user, task) { Export it also so we can call it from deprecated.coffee */ api.scoreTask = function(req, res, next) { - - // FIXME this is all uglified from coffeescript compile, clean this up - - var delta, direction, existing, id, task, user, _ref, _ref1, _ref2, _ref3, _ref4; - _ref = req.params, id = _ref.id, direction = _ref.direction; + var id = req.params.id, + direction = req.params.direction, + user = res.locals.user, + task; // Send error responses for improper API call - if (!id) { - return res.json(500, { - err: ':id required' - }); - } + if (!id) return res.json(500, {err: ':id required'}); if (direction !== 'up' && direction !== 'down') { - return res.json(500, { - err: ":direction must be 'up' or 'down'" - }); + return res.json(500, {err: ":direction must be 'up' or 'down'"}); } - user = res.locals.user; - /* If exists already, score it*/ - - if ((existing = user.tasks[id])) { - /* Set completed if type is daily or todo and task exists*/ - - if ((_ref1 = existing.type) === 'daily' || _ref1 === 'todo') { + // If exists already, score it + var existing; + if (existing = user.tasks[id]) { + // Set completed if type is daily or todo and task exists + if (existing.type === 'daily' || existing.type === 'todo') { existing.completed = direction === 'up'; } } else { - /* If it doesn't exist, this is likely a 3rd party up/down - create a new one, then score it*/ - + // If it doesn't exist, this is likely a 3rd party up/down - create a new one, then score it task = { id: id, value: 0, - type: ((_ref2 = req.body) != null ? _ref2.type : void 0) || 'habit', - text: ((_ref3 = req.body) != null ? _ref3.title : void 0) || id, + type: req.body.type || 'habit', + text: req.body.title || id, notes: "This task was created by a third-party service. Feel free to edit, it won't harm the connection to that service. Additionally, multiple services may piggy-back off this task." }; if (task.type === 'habit') { task.up = task.down = true; } - if ((_ref4 = task.type) === 'daily' || _ref4 === 'todo') { + if (task.type === 'daily' || task.type === 'todo') { task.completed = direction === 'up'; } addTask(user, task); } task = user.tasks[id]; - delta = algos.score(user, task, direction); + var delta = algos.score(user, task, direction); //user.markModified('flags'); user.save(function(err, saved) { if (err) return res.json(500, {err: err}); @@ -213,42 +133,29 @@ api.scoreTask = function(req, res, next) { }); }; -/* - Get all tasks -*/ - - +/** + * Get all tasks + */ api.getTasks = function(req, res, next) { - var tasks, types, _ref; - types = (_ref = req.query.type) === 'habit' || _ref === 'todo' || _ref === 'daily' || _ref === 'reward' ? [req.query.type] : ['habit', 'todo', 'daily', 'reward']; - tasks = _.toArray(_.filter(res.locals.user.tasks, function(t) { - var _ref1; - return _ref1 = t.type, __indexOf.call(types, _ref1) >= 0; - })); - return res.json(200, tasks); + if (req.query.type) { + return res.json(user[req.query.type+'s']); + } else { + return res.json(_.toArray(user.tasks)); + } }; -/* - Get Task -*/ - - +/** + * Get Task + */ api.getTask = function(req, res, next) { - var task; - task = res.locals.user.tasks[req.params.id]; - if (_.isEmpty(task)) { - return res.json(400, { - err: "No task found." - }); - } + var task = res.locals.user.tasks[req.params.id]; + if (_.isEmpty(task)) return res.json(400, {err: "No task found."}); return res.json(200, task); }; -/* - Delete Task -*/ - - +/** + * Delete Task + */ api.deleteTask = function(req, res, next) { deleteTask(res.locals.user, res.locals.task); res.locals.user.save(function(err) { @@ -263,113 +170,69 @@ api.deleteTask = function(req, res, next) { api.updateTask = function(req, res, next) { - var id, user; - user = res.locals.user; - id = req.params.id; - updateTask(user, id, req.body); - return user.save(function(err, saved) { - if (err) { - return res.json(500, { - err: err - }); - } - return res.json(200, _.findWhere(saved.toJSON().tasks, { - id: id - })); + var user = res.locals.user; + var task = user.tasks[req.params.id]; + user[task.type+'s'][_.findIndex(user[task.type+'s'],{id:task.id})] = req.body; + user.save(function(err, saved) { + if (err) return res.json(500, {err: err}) + return res.json(200, saved.tasks[id]); }); }; -/* - Update tasks (plural). This will update, add new, delete, etc all at once. - Should we keep this? -*/ - - +/** + * Update tasks (plural). This will update, add new, delete, etc all at once. + * TODO Should we keep this? + */ api.updateTasks = function(req, res, next) { - var tasks, user; - user = res.locals.user; - tasks = req.body; + var user = res.locals.user; + var tasks = req.body; _.each(tasks, function(task, idx) { if (task.id) { - /*delete*/ - + // delete if (task.del) { deleteTask(user, task); - task = { - deleted: true - }; + task = {deleted: true}; } else { - /* Update*/ - - updateTask(user, task.id, task); + // Update + // updateTask(user, task.id, task); //FIXME } } else { - /* Create*/ - + // Create task = addTask(user, task); } - return tasks[idx] = task; + tasks[idx] = task; }); - return user.save(function(err, saved) { - if (err) { - return res.json(500, { - err: err - }); - } + user.save(function(err, saved) { + if (err) return res.json(500, {err: err}); return res.json(201, tasks); }); }; api.createTask = function(req, res, next) { - var task, user; - user = res.locals.user; - task = addTask(user, req.body); - return user.save(function(err) { - if (err) { - return res.json(500, { - err: err - }); - } + var user = res.locals.user; + var task = addTask(user, req.body); + user.save(function(err, saved) { + if (err) return res.json(500, {err: err}); return res.json(201, task); }); }; api.sortTask = function(req, res, next) { - var from, id, path, to, type, user, _ref; - id = req.params.id; - _ref = req.body, to = _ref.to, from = _ref.from, type = _ref.type; - user = res.locals.user; - path = "" + type + "Ids"; - user[path].splice(to, 0, user[path].splice(from, 1)[0]); - return user.save(function(err, saved) { - if (err) { - return res.json(500, { - err: err - }); - } - return res.json(200, saved.toJSON()[path]); + var id = req.params.id; + var to = req.body.to, from = req.body.from, type = req.body.type; + var user = res.locals.user; + user[type+'s'].splice(to, 0, user[type+'s'].splice(from, 1)[0]); + user.save(function(err, saved) { + if (err) return res.json(500, {err: err}); + return res.json(200, saved.toJSON()[type+'s']); }); }; api.clearCompleted = function(req, res, next) { - var completedIds, todoIds, user; - user = res.locals.user; - completedIds = _.pluck(_.where(user.tasks, { - type: 'todo', - completed: true - }), 'id'); - todoIds = user.todoIds; - _.each(completedIds, function(id) { - delete user.tasks[id]; - return true; - }); - user.todoIds = _.difference(todoIds, completedIds); + var user = res.locals.user; + user.todos = _.where(user.todos, {completed: false}); return user.save(function(err, saved) { - if (err) { - return res.json(500, { - err: err - }); - } + if (err) return res.json(500, {err: err}); return res.json(saved); }); }; @@ -379,31 +242,21 @@ api.clearCompleted = function(req, res, next) { Items ------------------------------------------------------------------------ */ - - api.buy = function(req, res, next) { var hasEnough, type, user; user = res.locals.user; type = req.params.type; if (type !== 'weapon' && type !== 'armor' && type !== 'head' && type !== 'shield' && type !== 'potion') { - return res.json(400, { - err: ":type must be in one of: 'weapon', 'armor', 'head', 'shield', 'potion'" - }); + return res.json(400, {err: ":type must be in one of: 'weapon', 'armor', 'head', 'shield', 'potion'"}); } hasEnough = items.buyItem(user, type); if (hasEnough) { return user.save(function(err, saved) { - if (err) { - return res.json(500, { - err: err - }); - } + if (err) return res.json(500, {err: err}); return res.json(200, saved.toJSON().items); }); } else { - return res.json(200, { - err: "Not enough GP" - }); + return res.json(200, {err: "Not enough GP"}); } }; @@ -413,11 +266,9 @@ api.buy = function(req, res, next) { ------------------------------------------------------------------------ */ -/* - Get User -*/ - - +/** + * Get User + */ api.getUser = function(req, res, next) { var user = res.locals.user.toJSON(); user.stats.toNextLevel = algos.tnl(user.stats.lvl); @@ -430,12 +281,10 @@ api.getUser = function(req, res, next) { return res.json(200, user); }; -/* - Update user - FIXME add documentation here +/** + * Update user + * FIXME add documentation here */ - - api.updateUser = function(req, res, next) { var acceptableAttrs, errors, user; user = res.locals.user; @@ -483,8 +332,7 @@ api.updateUser = function(req, res, next) { }; api.cron = function(req, res, next) { - var user; - user = res.locals.user; + var user = res.locals.user; algos.cron(user); if (user.isModified()) { res.locals.wasModified = true; @@ -494,52 +342,36 @@ api.cron = function(req, res, next) { }; api.revive = function(req, res, next) { - var user; - user = res.locals.user; + var user = res.locals.user; algos.revive(user); - return user.save(function(err, saved) { - if (err) { - return res.json(500, { - err: err - }); - } + user.save(function(err, saved) { + if (err) return res.json(500, {err: err}); return res.json(200, saved); }); }; api.reroll = function(req, res, next) { - var user; - user = res.locals.user; - if (user.balance < 1) { - return res.json(401, { - err: "Not enough tokens." - }); - } + var user = res.locals.user; + if (user.balance < 1) return res.json(401, {err: "Not enough tokens."}); user.balance -= 1; - _.each(user.tasks, function(task) { - if (task.type !== 'reward') { - user.tasks[task.id].value = 0; - } - return true; - }); + _.each(['habits','dailys','todos'], function(type){ + _.each([user[type+'s']], function(task){ + task.value = 0; + }) + }) user.stats.hp = 50; - return user.save(function(err, saved) { - if (err) { - return res.json(500, { - err: err - }); - } + user.save(function(err, saved) { + if (err) return res.json(500, {err: err}); return res.json(200, saved); }); }; api.reset = function(req, res){ var user = res.locals.user; - user.tasks = {}; - - _.each(['habit', 'daily', 'todo', 'reward'], function(type) { - user[type + "Ids"] = []; - }); + user.habits = []; + user.dailys = []; + user.todos = []; + user.rewards = []; user.stats.hp = 50; user.stats.lvl = 1; @@ -675,9 +507,11 @@ api.deleteTag = function(req, res){ delete user.filters[tag.id]; user.tags.splice(i,1); // remove tag from all tasks - _.each(user.tasks, function(task) { - delete user.tasks[task.id].tags[tag.id]; - }); + _.each(['habits','dailys','todos','rewards'], function(type){ + _.each(user[type], function(task){ + delete task.tags[tag.id]; + }) + }) user.save(function(err,saved){ if (err) return res.json(500, {err: err}); // Need to use this until we found a way to update the ui for tasks when a tag is deleted @@ -695,22 +529,16 @@ api.deleteTag = function(req, res){ Run a bunch of updates all at once ------------------------------------------------------------------------ */ - - api.batchUpdate = function(req, res, next) { - var actions, oldJson, oldSend, performAction, user, _ref; - user = res.locals.user; - oldSend = res.send; - oldJson = res.json; - performAction = function(action, cb) { - /* - # TODO come up with a more consistent approach here. like: - # req.body=action.data; delete action.data; _.defaults(req.params, action) - # Would require changing action.dir on mobile app - */ + var user = res.locals.user; + var oldSend = res.send; + var oldJson = res.json; + var performAction = function(action, cb) { - var _ref; - req.params.id = (_ref = action.data) != null ? _ref.id : void 0; + // TODO come up with a more consistent approach here. like: + // req.body=action.data; delete action.data; _.defaults(req.params, action) + // Would require changing action.dir on mobile app + req.params.id = action.data && action.data.id; req.params.direction = action.dir; req.params.type = action.type; req.body = action.data; @@ -764,27 +592,22 @@ api.batchUpdate = function(req, res, next) { break; } }; - /* Setup the array of functions we're going to call in parallel with async*/ - actions = _.transform((_ref = req.body) != null ? _ref : [], function(result, action) { + // Setup the array of functions we're going to call in parallel with async + var actions = _.transform(req.body || [], function(result, action) { if (!_.isEmpty(action)) { - return result.push(function(cb) { - return performAction(action, cb); + result.push(function(cb) { + performAction(action, cb); }); } }); - /* call all the operations, then return the user object to the requester*/ - return async.series(actions, function(err) { - var response; + // call all the operations, then return the user object to the requester + async.series(actions, function(err) { res.json = oldJson; res.send = oldSend; - if (err) { - return res.json(500, { - err: err - }); - } - response = user.toJSON(); + if (err) return res.json(500, {err: err}); + var response = user.toJSON(); response.wasModified = res.locals.wasModified; if (response._tmp && response._tmp.drop) response.wasModified = true; @@ -794,7 +617,5 @@ api.batchUpdate = function(req, res, next) { }else{ res.json(200, {_v: response._v}); } - - return; }); }; \ No newline at end of file diff --git a/src/models/task.js b/src/models/task.js new file mode 100644 index 0000000000..8abdd9302e --- /dev/null +++ b/src/models/task.js @@ -0,0 +1,41 @@ +// User.js +// ======= +// Defines the user data model (schema) for use via the API. + +// Dependencies +// ------------ +var mongoose = require("mongoose"); +var Schema = mongoose.Schema; +var helpers = require('habitrpg-shared/script/helpers'); +var _ = require('lodash'); + +// Task Schema +// ----------- + +var TaskSchema = new Schema({ + history: [{date:Date, value:Number}], + _id:{type: String,'default': helpers.uuid}, + text: String, + notes: {type: String, 'default': ''}, + tags: Schema.Types.Mixed, //{ "4ddf03d9-54bd-41a3-b011-ca1f1d2e9371" : true }, + type: {type:String, 'default': 'habit'}, // habit, daily + up: {type: Boolean, 'default': true}, + down: {type: Boolean, 'default': true}, + value: {type: Number, 'default': 0}, + 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} +}); + +TaskSchema.methods.toJSON = function() { + var doc = this.toObject(); + doc.id = doc._id; + return doc; +} +TaskSchema.virtual('id').get(function(){ + return this._id; +}) + +module.exports.schema = TaskSchema; +module.exports.model = mongoose.model("Task", TaskSchema); \ No newline at end of file diff --git a/src/models/user.js b/src/models/user.js index 769d1f0450..74da652597 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -8,6 +8,7 @@ var mongoose = require("mongoose"); var Schema = mongoose.Schema; var helpers = require('habitrpg-shared/script/helpers'); var _ = require('lodash'); +var TaskSchema = require('./task').schema; // User Schema // ----------- @@ -204,26 +205,12 @@ var UserSchema = new Schema({ } ], - // ### Tasks Definition - // We can't define `tasks` until we move off Derby, since Derby requires dictionary of objects. When we're off, migrate - // to array of subdocs + challenges: [{type: 'String', ref:'Challenge'}], - tasks: Schema.Types.Mixed - /* - # history: {date, value} - # id - # notes - # tags { "4ddf03d9-54bd-41a3-b011-ca1f1d2e9371" : true }, - # text - # type - # up - # down - # value - # completed - # priority: '!!' - # repeat {m: true, t: true} - # streak - */ + habits: [TaskSchema], + dailys: [TaskSchema], + todos: [TaskSchema], + rewards: [TaskSchema], }, { strict: true, @@ -237,7 +224,8 @@ var UserSchema = new Schema({ // the underlying data will be modified too - so when we save back to the database, it saves it in the way Derby likes. // This will go away after the rewrite is complete -function transformTaskLists(doc) { +//FIXME use this in migration +/*function transformTaskLists(doc) { _.each(['habit', 'daily', 'todo', 'reward'], function(type) { // we use _.transform instead of a simple _.where in order to maintain sort-order doc[type + "s"] = _.reduce(doc[type + "Ids"], function(m, tid) { @@ -247,36 +235,37 @@ function transformTaskLists(doc) { return m; }, []); }); -} - -UserSchema.post('init', function(doc) { - transformTaskLists(doc); -}); +}*/ UserSchema.methods.toJSON = function() { var doc = this.toObject(); doc.id = doc._id; - transformTaskLists(doc); // we need to also transform for our server-side routes // FIXME? Is this a reference to `doc.filters` or just disabled code? Remove? - /* - // Remove some unecessary data as far as client consumers are concerned - //_.each(['habit', 'daily', 'todo', 'reward'], function(type) { - // delete doc["#{type}Ids"] - //}); - //delete doc.tasks - */ doc.filters = {}; doc._tmp = this._tmp; // be sure to send down drop notifs + // TODO why isnt' this happening automatically given the TaskSchema.methods.toJSON above? + _.each(['habits','dailys','todos','rewards'], function(type){ + _.each(doc[type],function(task){ + task.id = task._id; + }) + }) + return doc; }; +UserSchema.virtual('tasks').get(function () { + var tasks = this.habits.concat(this.dailys).concat(this.todos).concat(this.rewards); + var tasks = _.object(_.pluck(tasks,'id'), tasks); + return tasks; +}); + // FIXME - since we're using special @post('init') above, we need to flag when the original path was modified. // Custom setter/getter virtuals? UserSchema.pre('save', function(next) { - this.markModified('tasks'); + //this.markModified('tasks'); //FIXME //our own version incrementer this._v++; next(); diff --git a/src/routes/pages.js b/src/routes/pages.js index ab3e21cb5c..fe74e583d2 100644 --- a/src/routes/pages.js +++ b/src/routes/pages.js @@ -11,14 +11,6 @@ router.get('/', function(req, res) { }); }); -router.get('/partials/tasks', function(req, res) { - res.render('tasks/index', {env: res.locals.habitrpg}); -}); - -router.get('/partials/options', function(req, res) { - res.render('options', {env: res.locals.habitrpg}); -}); - // -------- Marketing -------- router.get('/splash.html', function(req, res) { diff --git a/views/index.jade b/views/index.jade index 1b6e26fab9..7e93871ae9 100644 --- a/views/index.jade +++ b/views/index.jade @@ -71,7 +71,6 @@ html script(type='text/javascript', src='/js/controllers/settingsCtrl.js') script(type='text/javascript', src='/js/controllers/statsCtrl.js') script(type='text/javascript', src='/js/controllers/tasksCtrl.js') - script(type='text/javascript', src='/js/controllers/taskDetailsCtrl.js') script(type='text/javascript', src='/js/controllers/filtersCtrl.js') script(type='text/javascript', src='/js/controllers/userCtrl.js') script(type='text/javascript', src='/js/controllers/groupsCtrl.js') @@ -91,6 +90,10 @@ html include ./shared/modals/index include ./shared/header/header + include ./shared/tasks/lists + include ./main/index + include ./options/index + #notification-area(ng-controller='NotificationCtrl') #wrap diff --git a/views/tasks/filters.jade b/views/main/filters.jade similarity index 100% rename from views/tasks/filters.jade rename to views/main/filters.jade diff --git a/views/main/index.jade b/views/main/index.jade new file mode 100644 index 0000000000..da4490409a --- /dev/null +++ b/views/main/index.jade @@ -0,0 +1,4 @@ +script(id='templates/habitrpg-main.html', type="text/ng-template") + include ./filters + div(ng-controller='TasksCtrl') + habitrpg-tasks(main='true', obj='user') diff --git a/views/options/index.jade b/views/options/index.jade index d27af6a886..c3ea045c1f 100644 --- a/views/options/index.jade +++ b/views/options/index.jade @@ -1,60 +1,64 @@ -.grid - .module.full-width - span.option-box.pull-right.wallet - include ../shared/gems +script(id='templates/habitrpg-options.html', type="text/ng-template") + .grid + .module.full-width + span.option-box.pull-right.wallet + include ../shared/gems - ul.nav.nav-tabs - li.active - a(data-toggle='tab', data-target='#profile-tab') - i.icon-user - | Profile - li - a(data-toggle='tab',data-target='#groups-tab', ng-click='fetchParty()') - i.icon-heart - | Groups - li(ng-show='user.flags.dropsEnabled') - a(data-toggle='tab',data-target='#inventory-tab') - i.icon-gift - | Inventory - li(ng-show='user.flags.dropsEnabled') - a(data-toggle='tab',data-target='#stable-tab') - i.icon-leaf - | Stable - li - a(data-toggle='tab',data-target='#tavern-tab',ng-click='fetchTavern()') - i.icon-eye-close - | Tavern - li - a(data-toggle='tab',data-target='#achievements-tab') - i.icon-certificate - | Achievements + ul.nav.nav-tabs + li.active + a(data-toggle='tab', data-target='#profile-tab') + i.icon-user + | Profile + li + a(data-toggle='tab',data-target='#groups-tab', ng-click='fetchParty()') + i.icon-heart + | Groups + li(ng-show='user.flags.dropsEnabled') + a(data-toggle='tab',data-target='#inventory-tab') + i.icon-gift + | Inventory + li(ng-show='user.flags.dropsEnabled') + a(data-toggle='tab',data-target='#stable-tab') + i.icon-leaf + | Stable + li + a(data-toggle='tab',data-target='#tavern-tab', ng-click='fetchTavern()') + i.icon-eye-close + | Tavern + li + a(data-toggle='tab',data-target='#achievements-tab') + i.icon-certificate + | Achievements + //-li + a(data-toggle='tab',data-target='#challenges-tab') + i.icon-bullhorn + | Challenges + li + a(data-toggle='tab',data-target='#settings-tab') + i.icon-wrench + | Settings - li - a(data-toggle='tab',data-target='#settings-tab') - i.icon-wrench - | Settings + .tab-content + .tab-pane.active#profile-tab + include ./profile - .tab-content - .tab-pane.active#profile-tab - include ./profile + .tab-pane#groups-tab + include ./groups/index - .tab-pane#groups-tab - include ./groups/index + .tab-pane#inventory-tab + include ./inventory - .tab-pane#inventory-tab - include ./inventory + .tab-pane#stable-tab + include ./pets - .tab-pane#stable-tab - include ./pets + .tab-pane#tavern-tab + include ./groups/tavern - .tab-pane#tavern-tab - include ./groups/tavern + .tab-pane#achievements-tab(ng-controller='UserCtrl') + include ../shared/profiles/achievements - .tab-pane#achievements-tab(ng-controller='UserCtrl') - include ../shared/profiles/achievements + .tab-pane#challenges-tab + include ./challenges/index - //
  • Challenges
  • - // app:challenges:main - - .tab-pane#settings-tab - include ./settings \ No newline at end of file + .tab-pane#settings-tab + include ./settings \ No newline at end of file diff --git a/views/tasks/index.jade b/views/shared/tasks/lists.jade similarity index 62% rename from views/tasks/index.jade rename to views/shared/tasks/lists.jade index a50a17b0cb..b43bd2df89 100644 --- a/views/tasks/index.jade +++ b/views/shared/tasks/lists.jade @@ -1,12 +1,14 @@ -div(ng-controller='TasksCtrl') - include ./filters +// Note here, we need this part of Habit to be a directive since we're going to be passing it variables from various +// parts of the app. The alternative would be to create new scopes for different containing sections, but that +// started to get unwieldy +script(id='templates/habitrpg-tasks.html', type="text/ng-template") .grid - .module(ng-controller='TasksCtrl', ng-repeat='list in taskLists', ng-class='{"rewards-module": list.type==="reward"}') + .module(ng-repeat='list in lists', ng-class='{"rewards-module": list.type==="reward"}') .task-column(class='{{list.type}}s') // Todos export/graph options - span.option-box.pull-right(ng-if='list.main && list.type=="todo"') - a.option-action(ng-show='user.history.todos', ng-click='toggleChart("todos")', tooltip='Progress') + span.option-box.pull-right(ng-if='main && list.type=="todo"') + a.option-action(ng-show='obj.history.todos', ng-click='toggleChart("todos")', tooltip='Progress') i.icon-signal //-a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{user.apiToken}}', tooltip='iCal') a.option-action(ng-click='notPorted()', tooltip='iCal', ng-show='false') @@ -14,7 +16,7 @@ div(ng-controller='TasksCtrl') // // Gold & Gems - span.option-box.pull-right.wallet(ng-if='list.main && list.type=="reward"') + span.option-box.pull-right.wallet(ng-if='main && list.type=="reward"') .money | {{gold(user.stats.gp)}} span.shop_gold(tooltip='Gold') @@ -29,18 +31,18 @@ div(ng-controller='TasksCtrl') .todos-chart(ng-if='list.type == "todo"', ng-show='charts.todos') // Add New - form.addtask-form.form-inline.new-task-form(name='new{{list.type}}form', ng-show='list.editable', ng-hide='list.showCompleted && list.type=="todo"', data-task-type='{{list.type}}', ng-submit='addTask(list)') + form.addtask-form.form-inline.new-task-form(name='new{{list.type}}form', ng-show='editable', ng-hide='list.showCompleted && list.type=="todo"', 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='user[list.type + "s"].length > 0', habitrpg-sortable) + ul(class='{{list.type}}s', ng-show='obj[list.type + "s"].length > 0', habitrpg-sortable) include ./task // Static Rewards - ul.items(ng-show='list.main && list.type=="reward" && user.flags.itemsEnabled') + ul.items(ng-show='main && list.type=="reward" && user.flags.itemsEnabled') li.task.reward-item(ng-hide='item.hide', ng-repeat='item in itemStore') // right-hand side control buttons .task-meta-controls @@ -59,14 +61,20 @@ div(ng-controller='TasksCtrl') br - include ./ads + // Ads + div(ng-if='main && !user.purchased.ads && list.type!="reward"') + span.pull-right + a(ng-click='modals.buyGems=true', tooltip='Remove Ads') + i.icon-remove + // Habit3 + ins.adsbygoogle(ng-init='initAds()', style='display: inline-block; width: 234px; height: 60px;', data-ad-client='ca-pub-3242350243827794', data-ad-slot='9529624576') // Todo Tabs - div(ng-if='list.type=="todo"', ng-class='{"tabbable tabs-below": list.type=="todo"}') + div(ng-if='main && list.type=="todo"', ng-class='{"tabbable tabs-below": list.type=="todo"}') button.task-action-btn.tile.spacious.bright(ng-show='list.showCompleted', ng-click='clearCompleted()') Clear Completed // remaining/completed tabs ul.nav.nav-tabs li(ng-class='{active: !list.showCompleted}') a(ng-click='list.showCompleted = false') Remaining li(ng-class='{active: list.showCompleted}') - a(ng-click='list.showCompleted= true') Complete + a(ng-click='list.showCompleted= true') Complete \ No newline at end of file diff --git a/views/tasks/task.jade b/views/shared/tasks/task.jade similarity index 80% rename from views/tasks/task.jade rename to views/shared/tasks/task.jade index c0a47fdb9b..aafb4d6bc6 100644 --- a/views/tasks/task.jade +++ b/views/shared/tasks/task.jade @@ -1,4 +1,4 @@ -li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,user.filters,user.preferences.dayStart,user.lastCron,list.showCompleted,list.main)}}', data-id='{{task.id}}') +li(ng-repeat='task in list.tasks', class='task {{taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', data-id='{{task.id}}') // right-hand side control buttons .task-meta-controls // Due Date @@ -12,25 +12,25 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use i.icon-tags(tooltip='{{appliedTags(user.tags, task.tags)}}', ng-hide='noTags(task.tags)') // edit - a(ng-hide='task._editing', ng-click='toggleEdit(task)', tooltip='Edit') + a(ng-hide='task._editing', ng-click='editTask(task)', tooltip='Edit') i.icon-pencil(ng-hide='task._editing') // cancel - a(ng-hide='!task._editing', ng-click='toggleEdit(task)', tooltip='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}} - // - // - // {{/}} + //- {{#if :task.challenge}} + //- {{#if brokenChallengeLink(:task)}} + //- + //- {{else}} + //- + //- {{/}} + //- {{else}} + //- + //- + //- {{/}} // delete - a(ng-click='remove(task)', tooltip='Delete') + a(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') @@ -43,34 +43,20 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use .task-controls.task-primary // Habits - span(ng-if='list.main && task.type=="habit"') - // only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges + span(ng-if='task.type=="habit"') a.task-action-btn(ng-if='task.up', ng-click='score(task,"up")') + a.task-action-btn(ng-if='task.down', ng-click='score(task,"down")') - - //span(ng-if='!list.main') - // span.task-action-btn(ng-show='task.up') + - // span.task-action-btn(ng-show='task.down') = // Rewards - span(ng-show='list.main && task.type=="reward"') - // only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges + span(ng-show='task.type=="reward"') a.money.btn-buy(ng-click='score(task, "down")') span.reward-cost {{task.value}} span.shop_gold - //span(ng-if='!list.main') - // span.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"') - // only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges - //span(ng-if='list.main') input.visuallyhidden.focusable(id='box-{{task.id}}', type='checkbox', ng-model='task.completed', ng-change='changeCheck(task)') label(for='box-{{task.id}}') - //span(ng-if='!list.main') - // input.visuallyhidden.focusable(id='box-{{task.id}}-static',type='checkbox', checked='false') - // label(for='box-{{task.id}}-static') // main content p.task-text // {{#if taskInChallenge(task)}} @@ -99,7 +85,7 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use // {{/}} // // {/} - form(ng-controller="TaskDetailsCtrl", ng-submit='save(task)') + form(ng-submit='saveTask(task)') // text & notes fieldset.option-group // {{#unless taskInChallenge(task)}} @@ -146,7 +132,7 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use legend.option-title Due Date input.option-content.datepicker(type='text', data-date-format='mm/dd/yyyy', ng-model='task.date') - fieldset.option-group(ng-if='list.main') + fieldset.option-group legend.option-title Tags label.checkbox(ng-repeat='tag in user.tags') input(type='checkbox', ng-model='task.tags[tag.id]') diff --git a/views/tasks/ads.jade b/views/tasks/ads.jade deleted file mode 100644 index 1622a3cb74..0000000000 --- a/views/tasks/ads.jade +++ /dev/null @@ -1,25 +0,0 @@ -div(ng-if='authenticated() && !user.purchased.ads') - span.pull-right(ng-if='list.type!="reward"') - a(ng-click='modals.buyGems=true', tooltip='Remove Ads') - i.icon-remove - - div(ng-if='list.type=="habit"', habitrpg-adsense) - script(async='async', src='//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js') - // Habit3 - ins.adsbygoogle(style='display: inline-block; width: 234px; height: 60px;', data-ad-client='ca-pub-3242350243827794', data-ad-slot='9529624576') - script. - (adsbygoogle = window.adsbygoogle || []).push({}); - - div(ng-if='list.type=="daily"', habitrpg-adsense) - script(async='async', src='//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js') - // Habit3 - ins.adsbygoogle(style='display: inline-block; width: 234px; height: 60px;', data-ad-client='ca-pub-3242350243827794', data-ad-slot='9529624576') - script. - (adsbygoogle = window.adsbygoogle || []).push({}); - - div(ng-if='list.type=="todo"', habitrpg-adsense) - script(async='async', src='//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js') - // Habit3 - ins.adsbygoogle(style='display: inline-block; width: 234px; height: 60px;', data-ad-client='ca-pub-3242350243827794', data-ad-slot='9529624576') - script. - (adsbygoogle = window.adsbygoogle || []).push({}); \ No newline at end of file