diff --git a/bower.json b/bower.json index 3f4eee4a3f..2d62dbefef 100644 --- a/bower.json +++ b/bower.json @@ -45,10 +45,10 @@ "angular-ui-router": "~0.2.13" }, "devDependencies": { - "angular-mocks": "1.3.3" + "angular-mocks": "1.3.9" }, "resolutions": { - "angular": "1.3.3", + "angular": "1.3.9", "jquery": ">=1.9.0" } } diff --git a/migrations/20150107_plan_dateUpdated_null.js b/migrations/20150107_plan_dateUpdated_null.js index ae231a0414..6e03b41a55 100644 --- a/migrations/20150107_plan_dateUpdated_null.js +++ b/migrations/20150107_plan_dateUpdated_null.js @@ -1,5 +1,8 @@ db.users.update( {'purchased.plan.customerId':{$ne:null}, 'purchased.plan.dateUpdated':null}, - {$set: {'purchased.plan.datedUpdated': new Date('12/01/2014')}}, + { + $set: {'purchased.plan.dateUpdated': new Date('12/01/2014')}, + $unset: {'purchased.plan.datedUpdated':''} + }, {multi:true} -); \ No newline at end of file +); diff --git a/public/css/tasks.styl b/public/css/tasks.styl index 7e50dfbcf3..f12308bd82 100644 --- a/public/css/tasks.styl +++ b/public/css/tasks.styl @@ -36,11 +36,16 @@ for $stage in $stages &:focus border: 1px solid darken($stage[1],30%) outline: 0 + .task-actions + background-color: darken($stage[1], 30%) .action-yesno label, - .task-action-btn + .task-action-btn, + .task-actions a background-color: darken($stage[1], 30%) &:hover, &:focus background-color: darken($stage[1], 40%) + .task-actions a:nth-of-type(2) + border-top: 1px solid darken($stage[1],50%) // If there are two habit buttons (+ -), add a border to separate them .task-options background-color: $stage[1] .option-group:not(.task-checklist) @@ -183,7 +188,7 @@ for $stage in $stages // task content .task-text display: block - padding: 0.75em 0 0.75em 3.5em + padding: 0.75em 0 0.75em 3.75em line-height: 1.4 word-wrap: break-word @@ -196,11 +201,6 @@ for $stage in $stages background-size:1.5em vertical-align: middle -.habit-wide .task-text - padding-left: 7em -.habit-narrow .task-text - padding-left: 0.75em - // when a task is being dragged .task.ui-sortable-helper box-shadow: 0 0 3px rgba(0,0,0,0.15), 0 0 5px rgba(0,0,0,0.1) @@ -486,7 +486,41 @@ form min-width: 2.5em .save-close text-align: center - @extend $hrpg-button + @extend $hrpg-button + +// Habits – task button styles (+ -) +.habits + .task-actions + display: inline-block + width: 3em + height: 100% + a + display: table; + width: 100% + height: 100% + font-size: 0.8em + span + display: table-cell + text-align: center + vertical-align: middle + width: 100% + height: 100% + &:hover, &:focus + color: #222 + text-decoration: none + .habit-wide + // When there are two buttons... + min-height: 4.5em + .task-actions + a + height: 50% + .habit-narrow + // When there are no buttons... + .task-actions + display: none + .task-text + padding-left: 1em + // todos ui // -------- diff --git a/public/js/app.js b/public/js/app.js index d97c3d3980..3ee68b2a9a 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1,9 +1,7 @@ "use strict"; window.habitrpg = angular.module('habitrpg', - ['ngResource', 'ngSanitize', 'userServices', 'groupServices', 'memberServices', 'challengeServices', - 'authServices', 'notificationServices', 'guideServices', 'authCtrl', 'paymentServices', - 'ui.bootstrap', 'ui.keypress', 'ui.router', 'chieffancypants.loadingBar', 'At', 'infinite-scroll', 'ui.select2', 'angular.filter']) + ['ui.bootstrap', 'ui.keypress', 'ui.router', 'chieffancypants.loadingBar', 'At', 'infinite-scroll', 'ui.select2', 'angular.filter', 'ngResource', 'ngSanitize']) // @see https://github.com/angular-ui/ui-router/issues/110 and https://github.com/HabitRPG/habitrpg/issues/1705 // temporary hack until they have a better solution @@ -14,8 +12,8 @@ window.habitrpg = angular.module('habitrpg', .constant("MOBILE_APP", false) //.constant("STORAGE_GROUPS_ID", "") // if we decide to take groups offline - .config(['$stateProvider', '$urlRouterProvider', '$httpProvider', '$provide', 'STORAGE_SETTINGS_ID', - function($stateProvider, $urlRouterProvider, $httpProvider, $provide, STORAGE_SETTINGS_ID) { + .config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'STORAGE_SETTINGS_ID', + function($stateProvider, $urlRouterProvider, $httpProvider, STORAGE_SETTINGS_ID) { $urlRouterProvider // Setup default selected tabs @@ -222,52 +220,4 @@ window.habitrpg = angular.module('habitrpg', $httpProvider.defaults.headers.common['x-api-user'] = settings.auth.apiId; $httpProvider.defaults.headers.common['x-api-key'] = settings.auth.apiToken; } - - // Handle errors - $provide.factory('myHttpInterceptor', ['$rootScope','$q',function($rootScope,$q) { - return { - response: function(response) { - return response; - }, - responseError: function(response) { - // Offline - if (response.status == 0 || - // don't know why we're getting 404 here, should be 0 - (response.status == 404 && _.isEmpty(response.data))) { - $rootScope.$broadcast('responseText', window.env.t('serverUnreach')); - - // Needs refresh - } else if (response.needRefresh) { - $rootScope.$broadcast('responseError', "The site has been updated and the page needs to refresh. The last action has not been recorded, please refresh and try again."); - - } else if (response.data.code && response.data.code === 'ACCOUNT_SUSPENDED') { - confirm(response.data.err); - localStorage.clear(); - window.location.href = '/logout'; - - // 400 range? - } else if (response.status < 500) { - $rootScope.$broadcast('responseText', response.data.err || response.data); - // Need to reject the prompse so the error is handled correctly - if (response.status === 401) { - return $q.reject(response); - } - - // Error - } else { - var error = 'Please reload, ' + - '"'+window.env.t('error')+' '+(response.data.err || response.data || 'something went wrong')+'" ' + - window.env.t('seeConsole'); - $rootScope.$broadcast('responseError', error); - console.error(response); - } - - return response; - // this completely halts the chain, meaning we can't queue offline actions - //if (canRecover(response)) return responseOrNewPromise - //return $q.reject(response); - } - }; - }]); - $httpProvider.interceptors.push('myHttpInterceptor'); }]) diff --git a/public/js/controllers/authCtrl.js b/public/js/controllers/authCtrl.js index bacc41c28f..a167ecb309 100644 --- a/public/js/controllers/authCtrl.js +++ b/public/js/controllers/authCtrl.js @@ -4,9 +4,9 @@ The authentication controller (login & facebook) */ -angular.module('authCtrl', []) - .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrlService', '$modal', - function($scope, $rootScope, User, $http, $location, $window, ApiUrlService, $modal) { +angular.module('habitrpg') + .controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrl', '$modal', + function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal) { $scope.logout = function() { localStorage.clear(); @@ -36,7 +36,7 @@ angular.module('authCtrl', []) if ($scope.registrationForm.$invalid) { return; } - var url = ApiUrlService.get() + "/api/v2/register"; + var url = ApiUrl.get() + "/api/v2/register"; if($rootScope.selectedLanguage) url = url + '?lang=' + $rootScope.selectedLanguage.code; $http.post(url, $scope.registerVals).success(function(data, status, headers, config) { runAuth(data.id, data.apiToken); @@ -48,7 +48,7 @@ angular.module('authCtrl', []) username: $scope.loginUsername || $('#login-tab input[name="username"]').val(), password: $scope.loginPassword || $('#login-tab input[name="password"]').val() }; - $http.post(ApiUrlService.get() + "/api/v2/user/auth/local", data) + $http.post(ApiUrl.get() + "/api/v2/user/auth/local", data) .success(function(data, status, headers, config) { runAuth(data.id, data.token); }).error(errorAlert); @@ -70,7 +70,7 @@ angular.module('authCtrl', []) if(email == null || email.length == 0) { alert(window.env.t('invalidEmail')); } else { - $http.post(ApiUrlService.get() + '/api/v2/user/reset-password', {email:email}) + $http.post(ApiUrl.get() + '/api/v2/user/reset-password', {email:email}) .success(function(){ alert(window.env.t('newPassSent')); }) @@ -120,7 +120,7 @@ angular.module('authCtrl', []) $scope.socialLogin = function(network){ hello(network).login({scope:'email'}).then(function(auth){ - $http.post(ApiUrlService.get() + "/api/v2/user/auth/social", auth) + $http.post(ApiUrl.get() + "/api/v2/user/auth/social", auth) .success(function(data, status, headers, config) { runAuth(data.id, data.token); }).error(errorAlert); diff --git a/public/js/controllers/footerCtrl.js b/public/js/controllers/footerCtrl.js index ccb4084860..e704785f86 100644 --- a/public/js/controllers/footerCtrl.js +++ b/public/js/controllers/footerCtrl.js @@ -1,108 +1,108 @@ "use strict"; -(typeof habitrpg !== 'undefined' ? habitrpg : habitrpgStatic) - .controller("FooterCtrl", ['$scope', '$rootScope', 'User', '$http', 'Notification', 'ApiUrlService', - function($scope, $rootScope, User, $http, Notification, ApiUrlService) { +angular.module('habitrpg').controller("FooterCtrl", +['$scope', '$rootScope', 'User', '$http', 'Notification', 'ApiUrl', +function($scope, $rootScope, User, $http, Notification, ApiUrl) { - if(typeof habitrpg === "undefined"){ - $scope.languages = env.avalaibleLanguages; - $scope.selectedLanguage = _.find(env.avalaibleLanguages, {code: env.language.code}); + if(typeof habitrpg === "undefined"){ + $scope.languages = env.avalaibleLanguages; + $scope.selectedLanguage = _.find(env.avalaibleLanguages, {code: env.language.code}); - $rootScope.selectedLanguage = $scope.selectedLanguage; - - $scope.changeLang = function(){ - window.location = '?lang='+$scope.selectedLanguage.code; - } + $rootScope.selectedLanguage = $scope.selectedLanguage; + + $scope.changeLang = function(){ + window.location = '?lang='+$scope.selectedLanguage.code; + } + } + + /** + External Scripts + JS files not needed right away (google charts) or entirely optional (analytics) + Each file gets loaded async via $.getScript, so it doesn't bog page-load + */ + $scope.deferredScripts = function(){ + + // Stripe + $.getScript('//checkout.stripe.com/v2/checkout.js'); + + // Google Analytics, only in production + if (window.env.NODE_ENV === 'production') { + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + ga('create', window.env.GA_ID, {userId:User.user._id}); + ga('require', 'displayfeatures'); + ga('send', 'pageview'); } - /** - External Scripts - JS files not needed right away (google charts) or entirely optional (analytics) - Each file gets loaded async via $.getScript, so it doesn't bog page-load - */ - $scope.deferredScripts = function(){ + // Scripts only for desktop + if (!window.env.IS_MOBILE) { + // Add This + //$.getScript("//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5016f6cc44ad68a4"); //FIXME why isn't this working when here? instead it's now in + var addthisServices = 'facebook,twitter,googleplus,tumblr,'+window.env.BASE_URL.replace('https://','').replace('http://',''); + window.addthis_config = { + ui_click: true, + services_custom:{ + name: "Download", + url: window.env.BASE_URL+"/export/avatar-"+User.user._id+".png", + icon: window.env.BASE_URL+"/favicon.ico" + }, + services_expanded:addthisServices, + services_compact:addthisServices + }; - // Stripe - $.getScript('//checkout.stripe.com/v2/checkout.js'); - - // Google Analytics, only in production - if (window.env.NODE_ENV === 'production') { - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - ga('create', window.env.GA_ID, {userId:User.user._id}); - ga('require', 'displayfeatures'); - ga('send', 'pageview'); - } - - // Scripts only for desktop - if (!window.env.IS_MOBILE) { - // Add This - //$.getScript("//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5016f6cc44ad68a4"); //FIXME why isn't this working when here? instead it's now in - var addthisServices = 'facebook,twitter,googleplus,tumblr,'+window.env.BASE_URL.replace('https://','').replace('http://',''); - window.addthis_config = { - ui_click: true, - services_custom:{ - name: "Download", - url: window.env.BASE_URL+"/export/avatar-"+User.user._id+".png", - icon: window.env.BASE_URL+"/favicon.ico" - }, - services_expanded:addthisServices, - services_compact:addthisServices - }; - - // Google Charts - $.getScript("//www.google.com/jsapi", function() { - google.load("visualization", "1", { - packages: ["corechart"], - callback: function() {} - }); + // Google Charts + $.getScript("//www.google.com/jsapi", function() { + google.load("visualization", "1", { + packages: ["corechart"], + callback: function() {} }); - } + }); } + } - /** - * Debug functions. Note that the server route for gems is only available if process.env.DEBUG=true - */ - if (_.contains(['development','test'],window.env.NODE_ENV)) { - $scope.setHealthLow = function(){ - User.set({ - 'stats.hp': 1 - }); - } - $scope.addMissedDay = function(){ - if (!confirm("Are you sure you want to reset the day?")) return; - var dayBefore = moment(User.user.lastCron).subtract(1, 'days').toDate(); - User.set({'lastCron': dayBefore}); - Notification.text('-1 day, remember to refresh'); - } - $scope.addTenGems = function(){ - $http.post(ApiUrlService.get() + '/api/v2/user/addTenGems').success(function(){ - User.log({}); - }) - } - $scope.addGold = function(){ - User.set({ - 'stats.gp': User.user.stats.gp + 500, - }); - } - $scope.addLevelsAndGold = function(){ - User.set({ - 'stats.exp': User.user.stats.exp + 10000, - 'stats.gp': User.user.stats.gp + 10000, - 'stats.mp': User.user.stats.mp + 10000 - }); - } - $scope.addOneLevel = function(){ - User.set({ - 'stats.exp': User.user.stats.exp + (Math.round(((Math.pow(User.user.stats.lvl, 2) * 0.25) + (10 * User.user.stats.lvl) + 139.75) / 10) * 10) - }); - } - $scope.addBossQuestProgressUp = function(){ - User.set({ - 'party.quest.progress.up': User.user.party.quest.progress.up + 1000 - }); - } + /** + * Debug functions. Note that the server route for gems is only available if process.env.DEBUG=true + */ + if (_.contains(['development','test'],window.env.NODE_ENV)) { + $scope.setHealthLow = function(){ + User.set({ + 'stats.hp': 1 + }); } - }]) + $scope.addMissedDay = function(){ + if (!confirm("Are you sure you want to reset the day?")) return; + var dayBefore = moment(User.user.lastCron).subtract(1, 'days').toDate(); + User.set({'lastCron': dayBefore}); + Notification.text('-1 day, remember to refresh'); + } + $scope.addTenGems = function(){ + $http.post(ApiUrl.get() + '/api/v2/user/addTenGems').success(function(){ + User.log({}); + }) + } + $scope.addGold = function(){ + User.set({ + 'stats.gp': User.user.stats.gp + 500, + }); + } + $scope.addLevelsAndGold = function(){ + User.set({ + 'stats.exp': User.user.stats.exp + 10000, + 'stats.gp': User.user.stats.gp + 10000, + 'stats.mp': User.user.stats.mp + 10000 + }); + } + $scope.addOneLevel = function(){ + User.set({ + 'stats.exp': User.user.stats.exp + (Math.round(((Math.pow(User.user.stats.lvl, 2) * 0.25) + (10 * User.user.stats.lvl) + 139.75) / 10) * 10) + }); + } + $scope.addBossQuestProgressUp = function(){ + User.set({ + 'party.quest.progress.up': User.user.party.quest.progress.up + 1000 + }); + } + } +}]) diff --git a/public/js/controllers/groupsCtrl.js b/public/js/controllers/groupsCtrl.js index da6f964683..376c25d810 100644 --- a/public/js/controllers/groupsCtrl.js +++ b/public/js/controllers/groupsCtrl.js @@ -212,7 +212,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' }); }]) - .controller('ChatCtrl', ['$scope', 'Groups', 'User', '$http', 'ApiUrlService', 'Notification', 'Members', '$rootScope', function($scope, Groups, User, $http, ApiUrlService, Notification, Members, $rootScope){ + .controller('ChatCtrl', ['$scope', 'Groups', 'User', '$http', 'ApiUrl', 'Notification', 'Members', '$rootScope', function($scope, Groups, User, $http, ApiUrl, Notification, Members, $rootScope){ $scope.message = {content:''}; $scope._sending = false; @@ -276,7 +276,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } //Chat.Chat.like({gid:group._id,mid:message.id}); - $http.post(ApiUrlService.get() + '/api/v2/groups/' + group._id + '/chat/' + message.id + '/like'); + $http.post(ApiUrl.get() + '/api/v2/groups/' + group._id + '/chat/' + message.id + '/like'); } $scope.flagChatMessage = function(groupId,message) { @@ -304,11 +304,18 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' 'level': window.env.t('sortLevel'), 'random': window.env.t('sortRandom'), 'pets': window.env.t('sortPets'), + 'habitrpg_date_joined' : window.env.t('sortHabitrpgJoined'), 'party_date_joined': window.env.t('sortJoined'), + 'habitrpg_last_logged_in': window.env.t('sortHabitrpgLastLoggedIn'), 'name': window.env.t('sortName'), 'backgrounds': window.env.t('sortBackgrounds'), }; + $scope.partyOrderAscendingChoices = { + 'ascending': window.env.t('ascendingSort'), + 'descending': window.env.t('descendingSort') + } + }]) .controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', diff --git a/public/js/controllers/hallCtrl.js b/public/js/controllers/hallCtrl.js index c135f5e8df..6956bfedd5 100644 --- a/public/js/controllers/hallCtrl.js +++ b/public/js/controllers/hallCtrl.js @@ -1,8 +1,8 @@ "use strict"; -habitrpg.controller("HallHeroesCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrlService', '$resource', - function($scope, $rootScope, User, Notification, ApiUrlService, $resource) { - var Hero = $resource(ApiUrlService.get() + '/api/v2/hall/heroes/:uid', {uid:'@_id'}); +habitrpg.controller("HallHeroesCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrl', '$resource', + function($scope, $rootScope, User, Notification, ApiUrl, $resource) { + var Hero = $resource(ApiUrl.get() + '/api/v2/hall/heroes/:uid', {uid:'@_id'}); $scope.hero = undefined; $scope.loadHero = function(uuid){ $scope.hero = Hero.get({uid:uuid}); @@ -19,9 +19,9 @@ habitrpg.controller("HallHeroesCtrl", ['$scope', '$rootScope', 'User', 'Notifica $scope.heroes = Hero.query(); }]); -habitrpg.controller("HallPatronsCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrlService', '$resource', - function($scope, $rootScope, User, Notification, ApiUrlService, $resource) { - var Patron = $resource(ApiUrlService.get() + '/api/v2/hall/patrons/:uid', {uid:'@_id'}); +habitrpg.controller("HallPatronsCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrl', '$resource', + function($scope, $rootScope, User, Notification, ApiUrl, $resource) { + var Patron = $resource(ApiUrl.get() + '/api/v2/hall/patrons/:uid', {uid:'@_id'}); var page = 0; $scope.patrons = []; diff --git a/public/js/controllers/headerCtrl.js b/public/js/controllers/headerCtrl.js index 197c48d8bf..ada83f24c2 100644 --- a/public/js/controllers/headerCtrl.js +++ b/public/js/controllers/headerCtrl.js @@ -28,12 +28,21 @@ habitrpg.controller("HeaderCtrl", ['$scope', 'Groups', 'User', case 'backgrounds': return member.preferences.background; break; + case 'habitrpg_date_joined': + return member.auth.timestamps.created; + break + case 'habitrpg_last_logged_in': + return member.auth.timestamps.loggedin; + break default: // party date joined return true; } } - ).reverse() + ) + if (User.user.party.orderAscending == "descending") { + $scope.partyMinusSelf = $scope.partyMinusSelf.reverse() + } }); } ]); diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index 2546f8ec0a..e46ffdbc1f 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -3,8 +3,8 @@ /* Make user and settings available for everyone through root scope. */ -habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrlService', 'Payments', - function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrlService, Payments) { +habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments', + function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments) { var user = User.user; var initSticky = _.once(function(){ @@ -222,7 +222,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ $scope.spell = null; $rootScope.applyingAction = false; - $http.post(ApiUrlService.get() + '/api/v2/user/class/cast/'+spell.key+'?targetType='+type+'&targetId='+targetId) + $http.post(ApiUrl.get() + '/api/v2/user/class/cast/'+spell.key+'?targetType='+type+'&targetId='+targetId) .success(function(){ var msg = window.env.t('youCast', {spell: spell.text()}); switch (type) { diff --git a/public/js/controllers/settingsCtrl.js b/public/js/controllers/settingsCtrl.js index 8b6f95b696..d033749620 100644 --- a/public/js/controllers/settingsCtrl.js +++ b/public/js/controllers/settingsCtrl.js @@ -2,8 +2,8 @@ // Make user and settings available for everyone through root scope. habitrpg.controller('SettingsCtrl', - ['$scope', 'User', '$rootScope', '$http', 'ApiUrlService', 'Guide', '$location', '$timeout', 'Notification', 'Shared', - function($scope, User, $rootScope, $http, ApiUrlService, Guide, $location, $timeout, Notification, Shared) { + ['$scope', 'User', '$rootScope', '$http', 'ApiUrl', 'Guide', '$location', '$timeout', 'Notification', 'Shared', + function($scope, User, $rootScope, $http, ApiUrl, Guide, $location, $timeout, Notification, Shared) { // FIXME we have this re-declared everywhere, figure which is the canonical version and delete the rest // $scope.auth = function (id, token) { @@ -81,10 +81,11 @@ habitrpg.controller('SettingsCtrl', if (!changeUser.newUsername || !changeUser.password) { return alert(window.env.t('fillAll')); } - $http.post(ApiUrlService.get() + '/api/v2/user/change-username', changeUser) + $http.post(ApiUrl.get() + '/api/v2/user/change-username', changeUser) .success(function(){ alert(window.env.t('usernameSuccess')); $scope.changeUser = {}; + User.sync(); }) .error(function(data){ alert(data.err); @@ -95,7 +96,7 @@ habitrpg.controller('SettingsCtrl', if (!changePass.oldPassword || !changePass.newPassword || !changePass.confirmNewPassword) { return alert(window.env.t('fillAll')); } - $http.post(ApiUrlService.get() + '/api/v2/user/change-password', changePass) + $http.post(ApiUrl.get() + '/api/v2/user/change-password', changePass) .success(function(data, status, headers, config){ if (data.err) return alert(data.err); alert(window.env.t('passSuccess')); @@ -132,7 +133,7 @@ habitrpg.controller('SettingsCtrl', } $scope['delete'] = function(){ - $http['delete'](ApiUrlService.get() + '/api/v2/user') + $http['delete'](ApiUrl.get() + '/api/v2/user') .success(function(res, code){ if (res.err) return alert(res.err); localStorage.clear(); @@ -141,14 +142,14 @@ habitrpg.controller('SettingsCtrl', } $scope.enterCoupon = function(code) { - $http.post(ApiUrlService.get() + '/api/v2/user/coupon/' + code).success(function(res,code){ + $http.post(ApiUrl.get() + '/api/v2/user/coupon/' + code).success(function(res,code){ if (code!==200) return; User.sync(); Notification.text('Coupon applied! Check your inventory'); }); } $scope.generateCodes = function(codes){ - $http.post(ApiUrlService.get() + '/api/v2/coupons/generate/'+codes.event+'?count='+(codes.count || 1)) + $http.post(ApiUrl.get() + '/api/v2/coupons/generate/'+codes.event+'?count='+(codes.count || 1)) .success(function(res,code){ $scope._codes = {}; if (code!==200) return; @@ -190,7 +191,7 @@ habitrpg.controller('SettingsCtrl', } $scope.applyCoupon = function(coupon){ - $http.get(ApiUrlService.get() + '/api/v2/coupons/valid-discount/'+coupon) + $http.get(ApiUrl.get() + '/api/v2/coupons/valid-discount/'+coupon) .success(function(){ Notification.text("Coupon applied!"); var subs = $scope.Content.subscriptionBlocks; diff --git a/public/js/controllers/tasksCtrl.js b/public/js/controllers/tasksCtrl.js index fd8bcfe0a9..cde069d877 100644 --- a/public/js/controllers/tasksCtrl.js +++ b/public/js/controllers/tasksCtrl.js @@ -1,7 +1,7 @@ "use strict"; -habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrlService', '$timeout', 'Shared', - function($scope, $rootScope, $location, User, Notification, $http, ApiUrlService, $timeout, Shared) { +habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Shared', + function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared) { $scope.obj = User.user; // used for task-lists $scope.user = User.user; @@ -88,7 +88,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $scope.unlink = function(task, keep) { // TODO move this to userServices, turn userSerivces.user into ng-resource - $http.post(ApiUrlService.get() + '/api/v2/user/tasks/' + task.id + '/unlink?keep=' + keep) + $http.post(ApiUrl.get() + '/api/v2/user/tasks/' + task.id + '/unlink?keep=' + keep) .success(function(){ User.log({}); }); @@ -202,8 +202,6 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $scope.shouldShow = function(task, list, prefs){ if (task._editing) // never hide a task while being edited return true; - if (task.type == 'todo') // TODO: convert To-Dos to use this new system and probably add a "Dated" column (i.e., "Incomplete" (includes dated), "Dated" (has due date and is not complete), "Complete") - return true; var shouldDo = task.type == 'daily' ? habitrpgShared.shouldDo(new Date, task.repeat, prefs) : true; switch (list.view) { case "yellowred": // Habits @@ -214,6 +212,8 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N return !task.completed && shouldDo; case "complete": // Dailies and To-Dos return task.completed || !shouldDo; + case "dated": // To-Dos + return !task.completed && task.date; case "ingamerewards": // All skills/rewards except the user's own return false; // Because "rewards" list includes only the user's own case "all": diff --git a/public/js/directives/directives.js b/public/js/directives/directives.js index d66dba6406..e4793d7e0c 100644 --- a/public/js/directives/directives.js +++ b/public/js/directives/directives.js @@ -81,8 +81,8 @@ habitrpg }, { header: window.env.t('todos'), type: 'todo', - placeHolder: window.env.t('newTodo') - // view: "remaining" + placeHolder: window.env.t('newTodo'), + view: "remaining" }, { header: window.env.t('rewards'), type: 'reward', diff --git a/public/js/services/authServices.js b/public/js/services/authServices.js deleted file mode 100644 index 46bfaddb0d..0000000000 --- a/public/js/services/authServices.js +++ /dev/null @@ -1,104 +0,0 @@ -'use strict'; - -/** - * Services that persists and retrieves user from localStorage. - * FIXME is this file ever used? - */ - -var facebook = {} - -angular.module('authServices', ['userServices']). -factory('Facebook', - ['$http', '$location', 'User', 'ApiUrlService', - function($http, $location, User, ApiUrlService) { - //TODO FB.init({appId: '${section.parameters['facebook.app.id']}', status: true, cookie: true, xfbml: true}); - var auth, user = User.user; - - facebook.handleStatusChange = function(session) { - if (session.authResponse) { - - FB.api('/me', { - fields: 'name, picture, email' - }, function(response) { - console.log(response.error) - if (!response.error) { - - var data = { - name: response.name, - facebook_id: response.id, - email: response.email - } - - $http.post(ApiUrlService.get() + '/api/v2/user/auth/facebook', data).success(function(data, status, headers, config) { - User.authenticate(data.id, data.token, function(err) { - if (!err) { - alert(window.env.t('loginSuccess')); - $location.path("/habit"); - } - }); - }).error(function(response) { - console.log('error') - }) - - } else { - alert('napaka') - } - //clearAction(); - }); - } else { - document.body.className = 'not_connected'; - //clearAction(); - } - } - - return { - - authUser: function() { - FB.Event.subscribe('auth.statusChange', facebook.handleStatusChange); - }, - - getAuth: function() { - return auth; - }, - - login: function() { - - FB.login(null, { - scope: 'email' - }); - }, - - logout: function() { - FB.logout(function(response) { - window.location.reload(); - }); - } - } - - } -]) - -.factory('LocalAuth', - ['$http', 'User', - function($http, User) { - var auth, - user = User.user; - - return { - getAuth: function() { - return auth; - }, - - login: function() { - user.id = ''; - user.apiToken = ''; - User.authenticate(); - return; - - }, - - logout: function() {} - } - - } -]); diff --git a/public/js/services/challengeServices.js b/public/js/services/challengeServices.js index 0b1d39eaf8..51b91de8c2 100644 --- a/public/js/services/challengeServices.js +++ b/public/js/services/challengeServices.js @@ -4,24 +4,23 @@ * Services that persists and retrieves user from localStorage. */ -angular.module('challengeServices', ['ngResource']). - factory('Challenges', ['ApiUrlService', '$resource', 'User', '$q', 'Members', - function(ApiUrlService, $resource, User, $q, Members) { - var Challenge = $resource(ApiUrlService.get() + '/api/v2/challenges/:cid', - {cid:'@_id'}, - { - //'query': {method: "GET", isArray:false} - join: {method: "POST", url: ApiUrlService.get() + '/api/v2/challenges/:cid/join'}, - leave: {method: "POST", url: ApiUrlService.get() + '/api/v2/challenges/:cid/leave'}, - close: {method: "POST", params: {uid:''}, url: ApiUrlService.get() + '/api/v2/challenges/:cid/close'}, - getMember: {method: "GET", url: ApiUrlService.get() + '/api/v2/challenges/:cid/member/:uid'} - }); +angular.module('habitrpg').factory('Challenges', +['ApiUrl', '$resource', +function(ApiUrl, $resource) { + var Challenge = $resource(ApiUrl.get() + '/api/v2/challenges/:cid', + {cid:'@_id'}, + { + //'query': {method: "GET", isArray:false} + join: {method: "POST", url: ApiUrl.get() + '/api/v2/challenges/:cid/join'}, + leave: {method: "POST", url: ApiUrl.get() + '/api/v2/challenges/:cid/leave'}, + close: {method: "POST", params: {uid:''}, url: ApiUrl.get() + '/api/v2/challenges/:cid/close'}, + getMember: {method: "GET", url: ApiUrl.get() + '/api/v2/challenges/:cid/member/:uid'} + }); - //var challenges = []; + //var challenges = []; - return { - Challenge: Challenge - //challenges: challenges - } - } -]); + return { + Challenge: Challenge + //challenges: challenges + } +}]); diff --git a/public/js/services/groupServices.js b/public/js/services/groupServices.js index 8707424a08..03fe1a708e 100644 --- a/public/js/services/groupServices.js +++ b/public/js/services/groupServices.js @@ -4,76 +4,74 @@ * Services that persists and retrieves user from localStorage. */ -angular.module('groupServices', ['ngResource', 'challengeServices']). - factory('Groups', ['ApiUrlService', '$resource', '$q', '$http', 'User', 'Challenges', - function(ApiUrlService, $resource, $q, $http, User, Challenges) { - - var Group = $resource(ApiUrlService.get() + '/api/v2/groups/:gid', - {gid:'@_id', messageId: '@_messageId'}, - { - get: { - method: "GET", - isArray:false, - // Wrap challenges as ngResource so they have functions like $leave or $join - transformResponse: function(data, headers) { - data = angular.fromJson(data); - _.each(data && data.challenges, function(c) { - angular.extend(c, Challenges.Challenge.prototype); - }); - return data; - } - }, - - postChat: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/chat'}, - deleteChatMessage: {method: "DELETE", url: ApiUrlService.get() + '/api/v2/groups/:gid/chat/:messageId'}, - flagChatMessage: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/chat/:messageId/flag'}, - clearFlagCount: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/chat/:messageId/clearflags'}, - join: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/join'}, - leave: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/leave'}, - invite: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/invite'}, - removeMember: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/removeMember'}, - questAccept: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/questAccept'}, - questReject: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/questReject'}, - questCancel: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/questCancel'}, - questAbort: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/questAbort'} +angular.module('habitrpg').factory('Groups', +['ApiUrl', '$resource', '$q', '$http', 'User', 'Challenges', +function(ApiUrl, $resource, $q, $http, User, Challenges) { + var Group = $resource(ApiUrl.get() + '/api/v2/groups/:gid', + {gid:'@_id', messageId: '@_messageId'}, + { + get: { + method: "GET", + isArray:false, + // Wrap challenges as ngResource so they have functions like $leave or $join + transformResponse: function(data, headers) { + data = angular.fromJson(data); + _.each(data && data.challenges, function(c) { + angular.extend(c, Challenges.Challenge.prototype); }); - - // Defer loading everything until they're requested - var data = {party: undefined, myGuilds: undefined, publicGuilds: undefined, tavern: undefined}; - - return { - party: function(cb){ - if (!data.party) return (data.party = Group.get({gid: 'party'}, cb)); - return (cb) ? cb(party) : data.party; - }, - publicGuilds: function(){ - //TODO combine these as {type:'guilds,public'} and create a $filter() to separate them - if (!data.publicGuilds) data.publicGuilds = Group.query({type:'public'}); - return data.publicGuilds; - }, - myGuilds: function(){ - if (!data.myGuilds) data.myGuilds = Group.query({type:'guilds'}); - return data.myGuilds; - }, - tavern: function(){ - if (!data.tavern) data.tavern = Group.get({gid:'habitrpg'}); - return data.tavern; - }, - - // On enter, set chat message to "seen" - seenMessage: function(gid){ - $http.post(ApiUrlService.get() + '/api/v2/groups/'+gid+'/chat/seen'); - if (User.user.newMessages) delete User.user.newMessages[gid]; - }, - - // Pass reference to party, myGuilds, publicGuilds, tavern; inside data in order to - // be able to modify them directly (otherwise will be stick with cached version) - data: data, - - Group: Group + return data; } - } - ]) + }, + + postChat: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/chat'}, + deleteChatMessage: {method: "DELETE", url: ApiUrl.get() + '/api/v2/groups/:gid/chat/:messageId'}, + flagChatMessage: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/chat/:messageId/flag'}, + clearFlagCount: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/chat/:messageId/clearflags'}, + join: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/join'}, + leave: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/leave'}, + invite: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/invite'}, + removeMember: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/removeMember'}, + questAccept: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questAccept'}, + questReject: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questReject'}, + questCancel: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questCancel'}, + questAbort: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questAbort'} + }); + + // Defer loading everything until they're requested + var data = {party: undefined, myGuilds: undefined, publicGuilds: undefined, tavern: undefined}; + + return { + party: function(cb){ + if (!data.party) return (data.party = Group.get({gid: 'party'}, cb)); + return (cb) ? cb(party) : data.party; + }, + publicGuilds: function(){ + //TODO combine these as {type:'guilds,public'} and create a $filter() to separate them + if (!data.publicGuilds) data.publicGuilds = Group.query({type:'public'}); + return data.publicGuilds; + }, + myGuilds: function(){ + if (!data.myGuilds) data.myGuilds = Group.query({type:'guilds'}); + return data.myGuilds; + }, + tavern: function(){ + if (!data.tavern) data.tavern = Group.get({gid:'habitrpg'}); + return data.tavern; + }, + + // On enter, set chat message to "seen" + seenMessage: function(gid){ + $http.post(ApiUrl.get() + '/api/v2/groups/'+gid+'/chat/seen'); + if (User.user.newMessages) delete User.user.newMessages[gid]; + }, + + // Pass reference to party, myGuilds, publicGuilds, tavern; inside data in order to + // be able to modify them directly (otherwise will be stick with cached version) + data: data, + + Group: Group + } +}]) /** * TODO Get this working. Make ChatService it's own ngResource, so we can update chat without having to sync the whole * group object (expensive). Also so we can add chat-specific routes diff --git a/public/js/services/guideServices.js b/public/js/services/guideServices.js index 3b808f0ba1..8e4eb1bc60 100644 --- a/public/js/services/guideServices.js +++ b/public/js/services/guideServices.js @@ -4,219 +4,217 @@ * Services for each tour step when you unlock features */ -angular.module('guideServices', []). - factory('Guide', ['$rootScope', 'User', '$timeout', function($rootScope, User, $timeout) { - - /** - * Init and show the welcome tour. Note we do it listening to a $rootScope broadcasted 'userLoaded' message, - * this because we need to determine whether to show the tour *after* the user has been pulled from the server, - * otherwise it's always start off as true, and then get set to false later - */ - $rootScope.$on('userUpdated', initTour); - function initTour(){ - if (User.user.flags.showTour === false) return; - var tourSteps = [ - { - element: ".main-herobox", - title: window.env.t('welcomeHabit'), - content: window.env.t('welcomeHabitT1') + " Justin, " + window.env.t('welcomeHabitT2'), - }, { - element: ".main-herobox", - title: window.env.t('yourAvatar'), - content: window.env.t('yourAvatarText'), - }, { - element: ".main-herobox", - title: window.env.t('avatarCustom'), - content: window.env.t('avatarCustomText'), - }, { - element: "#bars", - title: window.env.t('hitPoints'), - content: window.env.t('hitPointsText'), - }, { - element: "#bars", - title: window.env.t('expPoints'), - content: window.env.t('expPointsText'), - }, { - element: "ul.habits", - title: window.env.t('typeGoals'), - content: window.env.t('typeGoalsText'), - placement: "top" - }, { - element: "ul.habits", - title: window.env.t('habits'), - content: window.env.t('tourHabits'), - placement: "top" - }, { - element: "ul.dailys", - title: window.env.t('dailies'), - content: window.env.t('tourDailies'), - placement: "top" - }, { - element: "ul.todos", - title: window.env.t('todos'), - content: window.env.t('tourTodos'), - placement: "top" - }, { - element: "ul.main-list.rewards", - title: window.env.t('rewards'), - content: window.env.t('tourRewards'), - placement: "top" - }, { - element: "ul.habits li:first-child", - title: window.env.t('hoverOver'), - content: window.env.t('hoverOverText'), - placement: "right" - }, { - element: "ul.habits li:first-child", - title: window.env.t('unlockFeatures'), - content: window.env.t('unlockFeaturesT1') + " " + window.env.t('habitWiki') + " " + window.env.t('unlockFeaturesT2'), - placement: "right" - } - ]; - _.each(tourSteps, function(step){ - if (env.worldDmg.guide) { - step.content = "
" + step.content + "
"; - } else { - step.content = "
" + step.content + "
"; - } - }); - $('.main-herobox').popover('destroy'); - var tour = new Tour({ - template: "
" + - "

" + - "
" + - "" + - "" + - "
" + - "
", - onEnd: function(){ - User.set({'flags.showTour': false}); - } - }); - tourSteps.forEach(function(step) { - tour.addStep(_.defaults(step, {html: true})); - }); - tour.restart(); // Tour doesn't quite mesh with our handling of flags.showTour, just restart it on page load - //tour.start(true); - }; - - var alreadyShown = function(before, after) { - return !(!before && after === true); - }; - - var showPopover = function(selector, title, html, placement) { - if (!placement) placement = 'bottom'; - $(selector).popover('destroy'); - var button = ""; +angular.module('habitrpg').factory('Guide', +['$rootScope', 'User', '$timeout', +function($rootScope, User, $timeout) { + /** + * Init and show the welcome tour. Note we do it listening to a $rootScope broadcasted 'userLoaded' message, + * this because we need to determine whether to show the tour *after* the user has been pulled from the server, + * otherwise it's always start off as true, and then get set to false later + */ + $rootScope.$on('userUpdated', initTour); + function initTour(){ + if (User.user.flags.showTour === false) return; + var tourSteps = [ + { + element: ".main-herobox", + title: window.env.t('welcomeHabit'), + content: window.env.t('welcomeHabitT1') + " Justin, " + window.env.t('welcomeHabitT2'), + }, { + element: ".main-herobox", + title: window.env.t('yourAvatar'), + content: window.env.t('yourAvatarText'), + }, { + element: ".main-herobox", + title: window.env.t('avatarCustom'), + content: window.env.t('avatarCustomText'), + }, { + element: "#bars", + title: window.env.t('hitPoints'), + content: window.env.t('hitPointsText'), + }, { + element: "#bars", + title: window.env.t('expPoints'), + content: window.env.t('expPointsText'), + }, { + element: "ul.habits", + title: window.env.t('typeGoals'), + content: window.env.t('typeGoalsText'), + placement: "top" + }, { + element: "ul.habits", + title: window.env.t('habits'), + content: window.env.t('tourHabits'), + placement: "top" + }, { + element: "ul.dailys", + title: window.env.t('dailies'), + content: window.env.t('tourDailies'), + placement: "top" + }, { + element: "ul.todos", + title: window.env.t('todos'), + content: window.env.t('tourTodos'), + placement: "top" + }, { + element: "ul.main-list.rewards", + title: window.env.t('rewards'), + content: window.env.t('tourRewards'), + placement: "top" + }, { + element: "ul.habits li:first-child", + title: window.env.t('hoverOver'), + content: window.env.t('hoverOverText'), + placement: "right" + }, { + element: "ul.habits li:first-child", + title: window.env.t('unlockFeatures'), + content: window.env.t('unlockFeaturesT1') + " " + window.env.t('habitWiki') + " " + window.env.t('unlockFeaturesT2'), + placement: "right" + } + ]; + _.each(tourSteps, function(step){ if (env.worldDmg.guide) { - html = "
" + html + '
' + button + '
'; + step.content = "
" + step.content + "
"; } else { - html = "
" + html + '
' + button + '
'; + step.content = "
" + step.content + "
"; } - $(selector).popover({ - title: title, - placement: placement, - trigger: 'manual', - html: true, - content: html - }).popover('show'); - }; - - $rootScope.$watch('user.flags.customizationsNotification', function(after, before) { - if (alreadyShown(before, after)) return; - showPopover('.main-herobox', window.env.t('customAvatar'), window.env.t('customAvatarText'), 'bottom'); }); - - $rootScope.$watch('user.flags.itemsEnabled', function(after, before) { - if (alreadyShown(before, after)) return; - var html = window.env.t('storeUnlockedText'); - showPopover('div.rewards', window.env.t('storeUnlocked'), html, 'left'); - }); - - $rootScope.$watch('user.flags.partyEnabled', function(after, before) { - if (alreadyShown(before, after)) return; - var html = window.env.t('partySysText'); - showPopover('.user-menu', window.env.t('partySys'), html, 'bottom'); - }); - - $rootScope.$watch('user.flags.dropsEnabled', function(after, before) { - if (alreadyShown(before, after)) return; - var eggs = User.user.items.eggs || {}; - if (!eggs) { - eggs['Wolf'] = 1; // This is also set on the server + $('.main-herobox').popover('destroy'); + var tour = new Tour({ + template: "
" + + "

" + + "
" + + "" + + "" + + "
" + + "
", + onEnd: function(){ + User.set({'flags.showTour': false}); } - $rootScope.openModal('dropsEnabled'); }); - - $rootScope.$watch('user.flags.rebirthEnabled', function(after, before) { - if (alreadyShown(before, after)) return; - $rootScope.openModal('rebirthEnabled'); + tourSteps.forEach(function(step) { + tour.addStep(_.defaults(step, {html: true})); }); + tour.restart(); // Tour doesn't quite mesh with our handling of flags.showTour, just restart it on page load + //tour.start(true); + }; + + var alreadyShown = function(before, after) { + return !(!before && after === true); + }; + + var showPopover = function(selector, title, html, placement) { + if (!placement) placement = 'bottom'; + $(selector).popover('destroy'); + var button = ""; + if (env.worldDmg.guide) { + html = "
" + html + '
' + button + '
'; + } else { + html = "
" + html + '
' + button + '
'; + } + $(selector).popover({ + title: title, + placement: placement, + trigger: 'manual', + html: true, + content: html + }).popover('show'); + }; + + $rootScope.$watch('user.flags.customizationsNotification', function(after, before) { + if (alreadyShown(before, after)) return; + showPopover('.main-herobox', window.env.t('customAvatar'), window.env.t('customAvatarText'), 'bottom'); + }); + + $rootScope.$watch('user.flags.itemsEnabled', function(after, before) { + if (alreadyShown(before, after)) return; + var html = window.env.t('storeUnlockedText'); + showPopover('div.rewards', window.env.t('storeUnlocked'), html, 'left'); + }); + + $rootScope.$watch('user.flags.partyEnabled', function(after, before) { + if (alreadyShown(before, after)) return; + var html = window.env.t('partySysText'); + showPopover('.user-menu', window.env.t('partySys'), html, 'bottom'); + }); + + $rootScope.$watch('user.flags.dropsEnabled', function(after, before) { + if (alreadyShown(before, after)) return; + var eggs = User.user.items.eggs || {}; + if (!eggs) { + eggs['Wolf'] = 1; // This is also set on the server + } + $rootScope.openModal('dropsEnabled'); + }); + + $rootScope.$watch('user.flags.rebirthEnabled', function(after, before) { + if (alreadyShown(before, after)) return; + $rootScope.openModal('rebirthEnabled'); + }); - /** - * Classes Tour - */ - function classesTour(){ + /** + * Classes Tour + */ + function classesTour(){ - // TODO notice my hack-job `onShow: _.once()` functions. Without these, the syncronous path redirects won't properly handle showing tour - var tourSteps = [ - { - path: '/#/options/inventory/equipment', - onShow: _.once(function(tour){ - $timeout(function(){tour.goTo(0)}); - }), - element: '.equipment-tab', - title: window.env.t('classGear'), - content: window.env.t('classGearText', {klass: User.user.stats.class}) - }, - { - path: '/#/options/profile/stats', - onShow: _.once(function(tour){ - $timeout(function(){tour.goTo(1)}); - }), - element: ".allocate-stats", - title: window.env.t('stats'), - content: window.env.t('classStats'), - }, { - element: ".auto-allocate", - title: window.env.t('autoAllocate'), - placement: 'left', - content: window.env.t('autoAllocateText'), - }, { - element: ".meter.mana", - title: window.env.t('spells'), - content: window.env.t('spellsText') + " " + window.env.t('toDo') + "." - }, { - orphan: true, - title: window.env.t('readMore'), - content: window.env.t('moreClass') + " Wikia." - } - ]; - _.each(tourSteps, function(step){ - if (env.worldDmg.guide) { - step.content = "
" + step.content + "
"; - } else { - step.content = "
" + step.content + "
"; - } - }); - $('.allocate-stats').popover('destroy'); - var tour = new Tour({ + // TODO notice my hack-job `onShow: _.once()` functions. Without these, the syncronous path redirects won't properly handle showing tour + var tourSteps = [ + { + path: '/#/options/inventory/equipment', + onShow: _.once(function(tour){ + $timeout(function(){tour.goTo(0)}); + }), + element: '.equipment-tab', + title: window.env.t('classGear'), + content: window.env.t('classGearText', {klass: User.user.stats.class}) + }, + { + path: '/#/options/profile/stats', + onShow: _.once(function(tour){ + $timeout(function(){tour.goTo(1)}); + }), + element: ".allocate-stats", + title: window.env.t('stats'), + content: window.env.t('classStats'), + }, { + element: ".auto-allocate", + title: window.env.t('autoAllocate'), + placement: 'left', + content: window.env.t('autoAllocateText'), + }, { + element: ".meter.mana", + title: window.env.t('spells'), + content: window.env.t('spellsText') + " " + window.env.t('toDo') + "." + }, { + orphan: true, + title: window.env.t('readMore'), + content: window.env.t('moreClass') + " Wikia." + } + ]; + _.each(tourSteps, function(step){ + if (env.worldDmg.guide) { + step.content = "
" + step.content + "
"; + } else { + step.content = "
" + step.content + "
"; + } + }); + $('.allocate-stats').popover('destroy'); + var tour = new Tour({ // onEnd: function(){ // User.set({'flags.showTour': false}); // } - }); - tourSteps.forEach(function(step) { - tour.addStep(_.defaults(step, {html: true})); - }); - tour.restart(); // Tour doesn't quite mesh with our handling of flags.showTour, just restart it on page load - //tour.start(true); - }; + }); + tourSteps.forEach(function(step) { + tour.addStep(_.defaults(step, {html: true})); + }); + tour.restart(); // Tour doesn't quite mesh with our handling of flags.showTour, just restart it on page load + //tour.start(true); + }; - return { - initTour: initTour, - classesTour: classesTour - }; - - } -]); + return { + initTour: initTour, + classesTour: classesTour + }; +}]); diff --git a/public/js/services/memberServices.js b/public/js/services/memberServices.js index bb3a4c5ac5..53a1c1e998 100644 --- a/public/js/services/memberServices.js +++ b/public/js/services/memberServices.js @@ -4,85 +4,84 @@ * Services that persists and retrieves user from localStorage. */ -angular.module('memberServices', ['ngResource', 'sharedServices']). - factory('Members', ['$rootScope', 'Shared', 'ApiUrlService', '$resource', - function($rootScope, Shared, ApiUrlService, $resource) { - var members = {}; - var Member = $resource(ApiUrlService.get() + '/api/v2/members/:uid', {uid:'@_id'}); - var memberServices = { +angular.module('habitrpg').factory('Members', +['$rootScope', 'Shared', 'ApiUrl', '$resource', +function($rootScope, Shared, ApiUrl, $resource) { + var members = {}; + var Member = $resource(ApiUrl.get() + '/api/v2/members/:uid', {uid:'@_id'}); + var memberServices = { - Member: Member, + Member: Member, - members: members, + members: members, - /** - * Allows us to lazy-load party / group / public members throughout the application. - * @param obj - either a group or an individual member. If it's a group, we lazy-load all of its members. - */ - populate: function(obj){ + /** + * Allows us to lazy-load party / group / public members throughout the application. + * @param obj - either a group or an individual member. If it's a group, we lazy-load all of its members. + */ + populate: function(obj){ - function populateGroup(group){ - _.each(group.members, function(member){ - // meaning `populate('members')` wasn't run on the server, so we're getting the "in-database" form of - // the members array, which is just a list of IDs - not the populated objects - if (_.isString(member)) return; + function populateGroup(group){ + _.each(group.members, function(member){ + // meaning `populate('members')` wasn't run on the server, so we're getting the "in-database" form of + // the members array, which is just a list of IDs - not the populated objects + if (_.isString(member)) return; - // lazy-load - members[member._id] = member; - }) - } + // lazy-load + members[member._id] = member; + }) + } - // Array of groups - if (_.isArray(obj)) { - if (obj[0] && obj[0].members) { - _.each(obj, function(group){ - populateGroup(group); - }) - } - - // Individual Group - } else if (obj.members) - populateGroup(obj); - - // individual Member - if (obj._id) { - members[obj._id] = obj; - } - }, - - selectedMember: undefined, - - /** - * Once users are populated, we fetch them throughout the application (eg, modals). This - * either gets them or fetches if not available - * @param uid - */ - selectMember: function(uid, cb) { - var self = this; - // Fetch from cache if we can. For guild members, only their uname will have been fetched on initial load, - // check if they have full fields (eg, check profile.items and an item inside - // because sometimes profile.items exists but it's empty like when user is fetched for party - // and then for guild) - // and if not, fetch them - if (members[uid] && members[uid].items && members[uid].items.weapon) { - Shared.wrap(members[uid],false); - self.selectedMember = members[uid]; - cb(); - } else { - Member.get({uid: uid}, function(member){ - self.populate(member); // lazy load for later - Shared.wrap(member,false); - self.selectedMember = members[member._id]; - cb(); - }); - } - } + // Array of groups + if (_.isArray(obj)) { + if (obj[0] && obj[0].members) { + _.each(obj, function(group){ + populateGroup(group); + }) } - $rootScope.$on('userUpdated', function(event, user){ - memberServices.populate(user); - }) + // Individual Group + } else if (obj.members) + populateGroup(obj); - return memberServices; + // individual Member + if (obj._id) { + members[obj._id] = obj; } -]); + }, + + selectedMember: undefined, + + /** + * Once users are populated, we fetch them throughout the application (eg, modals). This + * either gets them or fetches if not available + * @param uid + */ + selectMember: function(uid, cb) { + var self = this; + // Fetch from cache if we can. For guild members, only their uname will have been fetched on initial load, + // check if they have full fields (eg, check profile.items and an item inside + // because sometimes profile.items exists but it's empty like when user is fetched for party + // and then for guild) + // and if not, fetch them + if (members[uid] && members[uid].items && members[uid].items.weapon) { + Shared.wrap(members[uid],false); + self.selectedMember = members[uid]; + cb(); + } else { + Member.get({uid: uid}, function(member){ + self.populate(member); // lazy load for later + Shared.wrap(member,false); + self.selectedMember = members[member._id]; + cb(); + }); + } + } + } + + $rootScope.$on('userUpdated', function(event, user){ + memberServices.populate(user); + }) + + return memberServices; +}]); diff --git a/public/js/services/notificationServices.js b/public/js/services/notificationServices.js index da2cd51afa..a0dde36fbb 100644 --- a/public/js/services/notificationServices.js +++ b/public/js/services/notificationServices.js @@ -1,85 +1,84 @@ /** Set up "+1 Exp", "Level Up", etc notifications */ -angular.module("notificationServices", []) - .factory("Notification", [function() { - var stack_topright = {"dir1": "down", "dir2": "left", "spacing1": 15, "spacing2": 15, "firstpos1": 60}; - function notify(html, type, icon) { - var notice = $.pnotify({ - type: type || 'warning', //('info', 'text', 'warning', 'success', 'gp', 'xp', 'hp', 'lvl', 'death', 'mp', 'crit') - text: html, - opacity: 1, - addclass: 'alert-' + type, - delay: 7000, - hide: (type == 'error') ? false : true, - mouse_reset: false, - width: "250px", - stack: stack_topright, - icon: icon || false - }).click(function() { notice.pnotify_remove() }); - }; +angular.module("habitrpg").factory("Notification", +[function() { + var stack_topright = {"dir1": "down", "dir2": "left", "spacing1": 15, "spacing2": 15, "firstpos1": 60}; + function notify(html, type, icon) { + var notice = $.pnotify({ + type: type || 'warning', //('info', 'text', 'warning', 'success', 'gp', 'xp', 'hp', 'lvl', 'death', 'mp', 'crit') + text: html, + opacity: 1, + addclass: 'alert-' + type, + delay: 7000, + hide: (type == 'error') ? false : true, + mouse_reset: false, + width: "250px", + stack: stack_topright, + icon: icon || false + }).click(function() { notice.pnotify_remove() }); + }; - /** - Show "+ 5 {gold_coin} 3 {silver_coin}" - */ - function coins(money) { - var absolute, gold, silver; - absolute = Math.abs(money); - gold = Math.floor(absolute); - silver = Math.floor((absolute - gold) * 100); - if (gold && silver > 0) { - return "" + gold + " " + silver + " "; - } else if (gold > 0) { - return "" + gold + " "; - } else if (silver > 0) { - return "" + silver + " "; - } - }; - - var sign = function(number){ - return number?number<0?'-':'+':'+'; + /** + Show "+ 5 {gold_coin} 3 {silver_coin}" + */ + function coins(money) { + var absolute, gold, silver; + absolute = Math.abs(money); + gold = Math.floor(absolute); + silver = Math.floor((absolute - gold) * 100); + if (gold && silver > 0) { + return "" + gold + " " + silver + " "; + } else if (gold > 0) { + return "" + gold + " "; + } else if (silver > 0) { + return "" + silver + " "; } + }; - var round = function(number){ - return Math.abs(number.toFixed(1)); - } - - return { - coins: coins, - hp: function(val) { - // don't show notifications if user dead - notify(sign(val) + " " + round(val) + " " + window.env.t('hp'), 'hp', 'glyphicon glyphicon-heart'); - }, - exp: function(val) { - if (val < -50) return; // don't show when they level up (resetting their exp) - notify(sign(val) + " " + round(val) + " " + window.env.t('xp'), 'xp', 'glyphicon glyphicon-star'); - }, - gp: function(val, bonus) { - notify(sign(val) + " " + coins(val - bonus), 'gp'); - }, - text: function(val){ - if (val) { - notify(val, 'info'); - } - }, - lvl: function(){ - notify(window.env.t('levelUp'), 'lvl', 'glyphicon glyphicon-chevron-up'); - }, - error: function(error){ - notify(error, "danger", 'glyphicon glyphicon-exclamation-sign'); - }, - mp: function(val) { - notify(sign(val) + " " + round(val) + " " + window.env.t('mp'), 'mp', 'glyphicon glyphicon-fire'); - }, - crit: function(val) { - notify(window.env.t('critBonus') + Math.round(val) + "%", 'crit', 'glyphicon glyphicon-certificate'); - }, - streak: function(val) { - notify(window.env.t('streakName') + ': ' + val, 'streak', 'glyphicon glyphicon-repeat'); - }, - drop: function(val) { - notify(val, 'drop', 'glyphicon glyphicon-gift'); - } - }; + var sign = function(number){ + return number?number<0?'-':'+':'+'; } -]); + + var round = function(number){ + return Math.abs(number.toFixed(1)); + } + + return { + coins: coins, + hp: function(val) { + // don't show notifications if user dead + notify(sign(val) + " " + round(val) + " " + window.env.t('hp'), 'hp', 'glyphicon glyphicon-heart'); + }, + exp: function(val) { + if (val < -50) return; // don't show when they level up (resetting their exp) + notify(sign(val) + " " + round(val) + " " + window.env.t('xp'), 'xp', 'glyphicon glyphicon-star'); + }, + gp: function(val, bonus) { + notify(sign(val) + " " + coins(val - bonus), 'gp'); + }, + text: function(val){ + if (val) { + notify(val, 'info'); + } + }, + lvl: function(){ + notify(window.env.t('levelUp'), 'lvl', 'glyphicon glyphicon-chevron-up'); + }, + error: function(error){ + notify(error, "danger", 'glyphicon glyphicon-exclamation-sign'); + }, + mp: function(val) { + notify(sign(val) + " " + round(val) + " " + window.env.t('mp'), 'mp', 'glyphicon glyphicon-fire'); + }, + crit: function(val) { + notify(window.env.t('critBonus') + Math.round(val) + "%", 'crit', 'glyphicon glyphicon-certificate'); + }, + streak: function(val) { + notify(window.env.t('streakName') + ': ' + val, 'streak', 'glyphicon glyphicon-repeat'); + }, + drop: function(val) { + notify(val, 'drop', 'glyphicon glyphicon-gift'); + } + }; +}]); diff --git a/public/js/services/paymentServices.js b/public/js/services/paymentServices.js index e0a059c07c..bc677b9155 100644 --- a/public/js/services/paymentServices.js +++ b/public/js/services/paymentServices.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('paymentServices',[]).factory('Payments', +angular.module('habitrpg').factory('Payments', ['$rootScope', 'User', '$http', 'Content', function($rootScope, User, $http, Content) { var Payments = {}; diff --git a/public/js/services/sharedServices.js b/public/js/services/sharedServices.js index 31ef0fc120..5694d56fb3 100644 --- a/public/js/services/sharedServices.js +++ b/public/js/services/sharedServices.js @@ -4,12 +4,10 @@ * Services that expose habitrpg-shared */ -angular.module('sharedServices', []). - factory('Shared', [function () { - return window.habitrpgShared; - } -]). - factory('Content', ['Shared', function (Shared) { - return Shared.content; - } -]); +angular.module('habitrpg') +.factory('Shared', [function () { + return window.habitrpgShared; +}]) +.factory('Content', ['Shared', function (Shared) { + return Shared.content; +}]); diff --git a/public/js/static.js b/public/js/static.js index 860dbf5587..3fdd1e6f2d 100644 --- a/public/js/static.js +++ b/public/js/static.js @@ -1,13 +1,12 @@ "use strict"; -window.habitrpgStatic = angular.module('habitrpgStatic', ['notificationServices', 'userServices', 'chieffancypants.loadingBar', 'authCtrl', 'ui.bootstrap']) +window.habitrpg = angular.module('habitrpg', ['chieffancypants.loadingBar', 'ui.bootstrap']) .constant("API_URL", "") .constant("STORAGE_USER_ID", 'habitrpg-user') .constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings') .constant("MOBILE_APP", false) .controller("RootCtrl", ['$scope', '$location', '$modal', '$http', function($scope, $location, $modal, $http){ - // must be #?memberId=xx, see https://github.com/angular/angular.js/issues/7239 var memberId = $location.search()['memberId']; if (memberId) { $http.get('/api/v2/members/'+memberId).success(function(data, status, headers, config){ diff --git a/public/manifest.json b/public/manifest.json index dd4e9abe2a..15deca809c 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -37,8 +37,8 @@ "js/env.js", "js/app.js", + "bower_components/habitrpg-shared/script/config.js", "js/services/sharedServices.js", - "js/services/authServices.js", "js/services/notificationServices.js", "bower_components/habitrpg-shared/script/userServices.js", "bower_components/habitrpg-shared/script/directives.js", diff --git a/src/models/user.js b/src/models/user.js index d136e31e6f..7cfc710474 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -254,6 +254,7 @@ var UserSchema = new Schema({ party: { // id // FIXME can we use a populated doc instead of fetching party separate from user? order: {type:String, 'default':'level'}, + orderAscending: {type:String, 'default':'ascending'}, quest: { key: String, progress: { diff --git a/src/routes/apiv2.coffee b/src/routes/apiv2.coffee index 6c93e3ba42..39d4523a75 100644 --- a/src/routes/apiv2.coffee +++ b/src/routes/apiv2.coffee @@ -607,7 +607,7 @@ module.exports = (swagger, v2) -> path: '/members/{uuid}' description: "Get a member." parameters: [path('uuid','Member ID','string')] - middleware: [auth.auth, i18n.getUserLanguage] + middleware: [i18n.getUserLanguage] # removed auth.auth, so anon users can view shared avatars action: members.getMember "/members/{uuid}/message": spec: diff --git a/views/options/profile.jade b/views/options/profile.jade index 59c48b0d6e..19a2ba48d1 100644 --- a/views/options/profile.jade +++ b/views/options/profile.jade @@ -213,6 +213,12 @@ script(id='partials/options.profile.profile.html', type='text/ng-template') button.btn.btn-default(ng-click='_editing.profile = true', ng-show='!_editing.profile')= env.t('edit') h4=env.t('displayName') span(ng-show='profile.profile.name') {{profile.profile.name}} + p + small.muted=env.t('displayNameDescription1') + |  + a(href='/#/options/settings/settings')=env.t('displayNameDescription2') + |  + =env.t('displayNameDescription3') span.muted(ng-hide='profile.profile.name') -  =env.t('none') |  - diff --git a/views/options/settings.jade b/views/options/settings.jade index 3fb026d635..e0b150c850 100644 --- a/views/options/settings.jade +++ b/views/options/settings.jade @@ -75,7 +75,7 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') h5.hint(popover=env.t('clockInfo'), popover-trigger='mouseenter')=env.t('customDayStart') .form-group .input-group - input.form-control(type='number', min='0', max='23', ng-model='user.preferences.dayStart', ng-change='saveDayStart()') + input.form-control(type='number', min='0', max='23', ng-model='user.preferences.dayStart', ng-blur='saveDayStart()') span.input-group-addon= ':00 (' + env.t('24HrClock') + ')' small =env.t('subWarning1') @@ -88,10 +88,26 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') .panel-heading span Registration .panel-body - p(ng-if='user.auth.facebook.id') Registered with Facebook + p(ng-if='user.auth.facebook.id')=env.t('registeredWithFb') div(ng-if='user.auth.local.username') - p Username: {{user.auth.local.username}} - p Email: {{user.auth.local.email}} + p=env.t('username') + |: {{user.auth.local.username}} + p + small.muted + =env.t('loginNameDescription1') + |  + a(href='/#/options/profile/profile')=env.t('loginNameDescription2') + |  + =env.t('loginNameDescription3') + p=env.t('email') + |: {{::user.auth.local.email}} + p + small.muted + =env.t('emailChange1') + |  + a(href='mailto:admin@habitrpg.com')=env.t('emailChange2') + |  + =env.t('emailChange3') hr h5=env.t('changeUsername') form(ng-submit='changeUsername(changeUser)', ng-show='user.auth.local') diff --git a/views/options/social/group.jade b/views/options/social/group.jade index 18ce8afe06..5d50ec3596 100644 --- a/views/options/social/group.jade +++ b/views/options/social/group.jade @@ -58,13 +58,20 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter button.pull-right.btn.btn-primary(ng-click="openModal('invite-friends', {controller:'GroupsCtrl'})", ng-if='::group.type=="party"') Invite Friends .panel-body.modal-fixed-height div.form-group(ng-if='::group.type=="party"') - =env.t('partyList') + p=env.t('partyList') select.form-control#partyOrder( ng-model='user.party.order', ng-controller='ChatCtrl', ng-options='k as v for (k , v) in partyOrderChoices', ng-change='set({"party.order": user.party.order})' ) + |  + select.form-control#partyOrderAscending( + ng-model='user.party.orderAscending', + ng-controller='ChatCtrl', + ng-options='k as v for (k , v) in partyOrderAscendingChoices', + ng-change='set({"party.orderAscending": user.party.orderAscending})' + ) table.table.table-striped(bindonce='group') tr(ng-repeat='member in group.members track by member._id') td.media diff --git a/views/shared/new-stuff.jade b/views/shared/new-stuff.jade index 81c719a57b..3b22a93917 100644 --- a/views/shared/new-stuff.jade +++ b/views/shared/new-stuff.jade @@ -1,26 +1,32 @@ -h5 TYRANNOSAUR PET QUEST, SPREAD THE WORD CHALLENGE, AND FIRST STRESS STRIKE! +h5 tr td - h5 Tyrannosaur Pet Quest - p In the Market there are now two new pet quests: King of the Dinosaurs and The Dinosaur Unearthed! They both give out the same rewards, including pet Tyrannosaur eggs. The difference is that "King of the Dinosaurs" is a normal pet quest, like all the others, whereas "The Dinosaur Unearthed" has less HP - but also a Rage bar (a la World Bosses) that allows it to heal if you skip too many of your Dailies. Both bosses still attack your party based on how many Dailies are incomplete. Users will be able to buy Tyrannosaur eggs after defeating either boss twice or both bosses once. - p Have fun! - p.small.muted by Baconsaur, Urse, Lemoness, and SabreCat - tr - td - h5 Spread the Word Challenge Reminder - p In case you missed it, we're running our second Spread the Word Challenge! The rules are simple: make a post some time between December 31st 2014 and January 31st 2015 on some form of blog or social media that tells people about HabitRPG. The top post will be awarded 100 GEMS, and the next nineteen top posts will be awarded 80 GEMS each. Learn more and join in here! - tr - td - h5 World Boss: First Stress Strike! - p The World Boss in the Tavern has used its first Stress Strike! - p Despite our best efforts, we've let some Dailies get away from us, and their dark-red color has infuriated the Abominable Stressbeast and caused it to regain some of its health! The horrible creature lunges for the Stables, but Matt the Beast Master heroically leaps into the fray to protect the pets and mounts. The Stressbeast has seized Matt in its vicious grip, but at least it's distracted for the moment. - p Complete Dailies and To-Dos to damage the World Boss! Incomplete Dailies fill the Stress Strike Bar. When the Stress Strike bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts who are not resting in the inn will have their incomplete Dailies tallied. - p.small.muted by Lemoness, Kiwibot, and SabreCat - + h5 1/19/2015 - WORLD BOSS: SECOND STRESS STRIKE! + p AHHHHHHHH!!!!! IT'S GOT ME!!!!! Oh, Habiticans, why didn't you do your Dailies?! + p The World Boss in the Tavern has used another Stress Strike, and this time it's attacked me, Bailey the Town Crier! To save me and the other NPCs, complete Dailies and To-Dos to damage the World Boss! Incomplete Dailies fill the Stress Strike Bar. When the Stress Strike bar is full, the World Boss will attack an NPC and regain some health. A World Boss will never damage individual players or accounts in any way. Only active accounts who are not resting in the inn will have their incomplete Dailies tallied. + p by Lemoness, Kiwibot, and SabreCat hr a(href='/static/old-news', target='_blank') Read older news mixin oldNews + h5 1/15/2015 + tr + td + h5 Tyrannosaur Pet Quest + p In the Market there are now two new pet quests: King of the Dinosaurs and The Dinosaur Unearthed! They both give out the same rewards, including pet Tyrannosaur eggs. The difference is that "King of the Dinosaurs" is a normal pet quest, like all the others, whereas "The Dinosaur Unearthed" has less HP - but also a Rage bar (a la World Bosses) that allows it to heal if you skip too many of your Dailies. Both bosses still attack your party based on how many Dailies are incomplete. Users will be able to buy Tyrannosaur eggs after defeating either boss twice or both bosses once. + p Have fun! + p.small.muted by Baconsaur, Urse, Lemoness, and SabreCat + tr + td + h5 Spread the Word Challenge Reminder + p In case you missed it, we're running our second Spread the Word Challenge! The rules are simple: make a post some time between December 31st 2014 and January 31st 2015 on some form of blog or social media that tells people about HabitRPG. The top post will be awarded 100 GEMS, and the next nineteen top posts will be awarded 80 GEMS each. Learn more and join in here! + tr + td + h5 World Boss: First Stress Strike! + p The World Boss in the Tavern has used its first Stress Strike! + p Despite our best efforts, we've let some Dailies get away from us, and their dark-red color has infuriated the Abominable Stressbeast and caused it to regain some of its health! The horrible creature lunges for the Stables, but Matt the Beast Master heroically leaps into the fray to protect the pets and mounts. The Stressbeast has seized Matt in its vicious grip, but at least it's distracted for the moment. + p Complete Dailies and To-Dos to damage the World Boss! Incomplete Dailies fill the Stress Strike Bar. When the Stress Strike bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts who are not resting in the inn will have their incomplete Dailies tallied. + p.small.muted by Lemoness, Kiwibot, and SabreCat h5 1/8/2015 tr td diff --git a/views/shared/tasks/lists.jade b/views/shared/tasks/lists.jade index e02ef29de3..ae28b99918 100644 --- a/views/shared/tasks/lists.jade +++ b/views/shared/tasks/lists.jade @@ -23,7 +23,7 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") .todos-chart(ng-if='::list.type == "todo"', ng-show='charts.todos') // Add New - form.task-add(name='new{{list.type}}form', ng-hide='obj._locked || (list.showCompleted && list.type=="todo")', ng-submit='addTask(obj[list.type+"s"],list)') + form.task-add(name='new{{list.type}}form', ng-hide='obj._locked', ng-submit='addTask(obj[list.type+"s"],list)') input(type='text', ng-model='list.newTask', placeholder='{{list.placeHolder}}', required) button(type='submit', ng-disabled='new{{list.type}}form.$invalid') span.glyphicon.glyphicon-plus @@ -50,20 +50,20 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") a(ng-click='list.view = "complete"')=env.t('grey') // Todo Tabs div(ng-if='::main && list.type=="todo"', ng-class='::{"tabbable tabs-below": list.type=="todo"}') - // div(ng-show='list.view == "complete" || list.view == "all"') - // li.task.reward-item(ng-if='#{canceler ? "user.stats.buffs."+canceler : "user.items.special."+k+">0"}',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.#{k}.notes()}}') if position=="bottom" - div(ng-show='list.showCompleted') + div(ng-show='list.view == "complete"') .alert =env.t('lotOfToDos') button.task-action-btn.tile.spacious.bright(ng-click='user.ops.clearCompleted({})',popover=env.t('deleteToDosExplanation'),popover-trigger='mouseenter')=env.t('clearCompleted') p!=env.t('beeminderDeleteWarning') // remaining/completed tabs ul.task-filter - li(ng-class='{active: !list.showCompleted}') - a(ng-click='list.showCompleted = false')=env.t('remaining') - li(ng-class='{active: list.showCompleted}') - a(ng-click='list.showCompleted= true')=env.t('complete') + li(ng-class='{active: list.view == "remaining"}') + a(ng-click='list.view = "remaining"')=env.t('remaining') + li(ng-class='{active: list.view == "dated"}', tooltip=env.t('datedNotSorted')) + a(ng-click='list.view = "dated"')=env.t('dated') + li(ng-class='{active: list.view == "complete"}') + a(ng-click='list.view = "complete"')=env.t('complete') // Rewards Tabs div(ng-if='::main && list.type=="reward"', class='tabbable tabs-below') ul.task-filter diff --git a/views/shared/tasks/task.jade b/views/shared/tasks/task.jade index 20a48ed63c..988402a6be 100644 --- a/views/shared/tasks/task.jade +++ b/views/shared/tasks/task.jade @@ -1,4 +1,4 @@ -li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s"]', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)', ng-class='{"cast-target":spell && (list.type != "reward")}', popover-trigger='mouseenter', data-popover-html="{{task.notes | markdown}}", data-popover-placement="top", ng-show='shouldShow(task, list, user.preferences)') +li(bindonce='list', bo-id='"task-"+task.id', ng-repeat='task in obj[list.type+"s"]', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)', ng-class='{"cast-target":spell && (list.type != "reward")}', popover-trigger='mouseenter', data-popover-html="{{task.notes | markdown}}", data-popover-placement="top", ng-show='shouldShow(task, list, user.preferences)') // right-hand side control buttons .task-meta-controls @@ -61,10 +61,12 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s .task-controls.task-primary(ng-if='!task._editing') // Habits - span(ng-if='::task.type=="habit"') + .task-actions(ng-if='::task.type=="habit"') // score() is overridden in challengesCtrl to do nothing - 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")') - + a(ng-if='task.up', ng-click='applyingAction || score(task,"up")') + span.glyphicon.glyphicon-plus + a(ng-if='task.down', ng-click='applyingAction || score(task,"down")') + span.glyphicon.glyphicon-minus // Rewards span(ng-show='task.type=="reward"') diff --git a/views/static/layout.jade b/views/static/layout.jade index 3c8bd38bd7..8744b06518 100644 --- a/views/static/layout.jade +++ b/views/static/layout.jade @@ -1,7 +1,7 @@ //-Trick needed to pass 'env' to ./layout block vars doctype html -html(ng-app='habitrpgStatic') +html(ng-app='habitrpg') head block extraHead diff --git a/views/static/new-stuff.jade b/views/static/new-stuff.jade index 9cbf77b87f..6757b48f68 100644 --- a/views/static/new-stuff.jade +++ b/views/static/new-stuff.jade @@ -1,6 +1,6 @@ block vars doctype html -html(ng-app='habitrpgStatic') +html(ng-app='habitrpg') head block title