diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 56e37cfe5f..463face2ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,5 +14,4 @@ HabitRPG uses [Trello](https://trello.com/b/EpoYEYod/habitrpg) to track feature # Contributing Code -* See [Contributing to HabitRPG](http://habitrpg.wikia.com/wiki/Contributing_to_HabitRPG) -* See [Tips](http://habitrpg.wikia.com/wiki/Tips) +* See [Contributing to HabitRPG](http://habitrpg.wikia.com/wiki/Contributing_to_HabitRPG#Coders_.28Web_.26_Mobile.29) diff --git a/Gruntfile.js b/Gruntfile.js index 0d7247aa83..d778230751 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,6 +4,8 @@ module.exports = function(grunt) { // Project configuration. grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + git_changelog: { minimal: { options: { @@ -56,7 +58,10 @@ module.exports = function(grunt) { build: { files: [ {expand: true, cwd: 'public/', src: 'favicon.ico', dest: 'build/'}, - {expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/dist/spritesmith-*.png', dest: 'build/'} + {expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/dist/spritesmith.png', dest: 'build/'}, + {expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/img/sprites/backer-only/*.gif', dest: 'build/'}, + {expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/img/sprites/npc_ian.gif', dest: 'build/'}, + {expand: true, cwd: 'public/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'build/'} ] } }, @@ -69,16 +74,18 @@ module.exports = function(grunt) { }, src: [ 'build/*.js', 'build/*.css', 'build/favicon.ico', - 'build/bower_components/bootstrap/docs/assets/css/*.css', - 'build/bower_components/habitrpg-shared/dist/*.css' + 'build/bower_components/habitrpg-shared/dist/*.png', + 'build/bower_components/habitrpg-shared/img/sprites/backer-only/*.gif', + 'build/bower_components/habitrpg-shared/img/sprites/npc_ian.gif', + 'build/bower_components/bootstrap/dist/fonts/*' ], - dest: 'make-sure-i-do-not-exist' + dest: 'build/*.css' } }, - nodemon: { + nodemon: { dev: { - ignoredFiles: ['public/*', 'Gruntfile.js', 'CHANGELOG.md', 'views/*', 'build/*', '.idea*', '.git*', '*.log'] + script: '<%= pkg.main %>' } }, @@ -106,27 +113,35 @@ module.exports = function(grunt) { var files = grunt.file.readJSON('./public/manifest.json'); var uglify = {}; var cssmin = {}; + _.each(files, function(val, key){ + var js = uglify['build/' + key + '.js'] = []; + _.each(files[key]['js'], function(val){ js.push('public/' + val); }); + + var css = cssmin['build/' + key + '.css'] = []; + _.each(files[key]['css'], function(val){ - if(val == 'app.css' || val == 'static.css'){ - cssmin['build/' + val] = ['build/' + val] - }else{ - cssmin['build/' + val] = ['public/' + val] - } + var path = (val == 'app.css' || val == 'static.css') ? 'build/' : 'public/'; + css.push(path + val) }); + }); + grunt.config.set('uglify.build.files', uglify); - grunt.config.set('uglify.build.options', {compress: false}) + grunt.config.set('uglify.build.options', {compress: false}); + grunt.config.set('cssmin.build.files', cssmin); + // Rewrite urls to relative path + grunt.config.set('cssmin.build.options', {'target': 'public/css/whatever-css.css'}); }); // Register tasks. grunt.registerTask('build:prod', ['loadManifestFiles', 'clean:build', 'uglify', 'stylus', 'cssmin', 'copy:build', 'hashres']); - grunt.registerTask('build:dev', ['loadManifestFiles', 'clean:build', 'stylus', 'cssmin', 'copy:build']); + grunt.registerTask('build:dev', ['stylus']); grunt.registerTask('run:dev', [ 'build:dev', 'concurrent' ]); diff --git a/bower.json b/bower.json index e9f2e6ae37..a6de245fda 100644 --- a/bower.json +++ b/bower.json @@ -17,9 +17,9 @@ "jquery": "~2.1.0", "jquery.cookie": "~1.4.0", "jquery-ui": "~1.10.3", - "angular": "~1.2.11", - "angular-sanitize": "~1.2.11", - "angular-resource": "~1.2.11", + "angular": "~1.2.12", + "angular-sanitize": "~1.2.12", + "angular-resource": "~1.2.12", "angular-ui-utils": "~0.1.0", "angular-ui-select2": "https://github.com/angular-ui/ui-select2.git#4469fd7a32eba2e28d5e3dd319b49e1ad090b167", "angular-bootstrap": "~0.10.0", @@ -36,13 +36,12 @@ "JavaScriptButtons": "git://github.com/paypal/JavaScriptButtons.git#master", "Angular-At-Directive": "git://github.com/snicker/Angular-At-Directive#master", "js-emoji": "git://github.com/snicker/js-emoji#master", - "gemoji": "git://github.com/github/gemoji", "sticky": "*", "swagger-ui": "https://github.com/wordnik/swagger-ui.git#105c516f5f055b140e935e9cfe0c36f841921dff", "ngInfiniteScroll": "~1.0.0", "jquery-colorbox": "~1.4.36" }, "devDependencies": { - "angular-mocks": "~1.2.11" + "angular-mocks": "~1.2.12" } } diff --git a/migrations/contribs_plan.js b/migrations/contribs_plan.js new file mode 100644 index 0000000000..a08bd93f76 --- /dev/null +++ b/migrations/contribs_plan.js @@ -0,0 +1,23 @@ +// Give contrib.level 7+ free subscription for life +db.users.update( + + { + 'contributor.level':{$gte:7}, + 'purchased.plan.customerId':null + }, + + { + $set: { + 'purchased.plan':{ + planId: 'basic', + customerId: 'habitrpg', + dateCreated: new Date, + dateUpdated: new Date, + gemsBought: 0 + } + } + }, + + {multi:true} + +) \ No newline at end of file diff --git a/public/css/alerts.styl b/public/css/alerts.styl index 1520f695f8..40a7b8910b 100644 --- a/public/css/alerts.styl +++ b/public/css/alerts.styl @@ -33,6 +33,10 @@ alert-sm-mixin() .alert alert-sm-mixin() + .notification-icon + transform: scale(0.8) + + // variables gpColor = #FBEFC0 @@ -95,35 +99,6 @@ borderDarken = 20% border-color: darken(dropColor,borderDarken) color: dropText -// alert icons - -.icon-gold - background: url("/bower_components/habitrpg-shared/img/coin_single_gold.png") no-repeat - background-position: center center - background-size: 18px - width: 14px - height: 14px - display: inline-block - vertical-align: middle - -.icon-silver - background: url("/bower_components/habitrpg-shared/img/coin_single_silver.png") no-repeat - background-position: center center - background-size: 18px - width: 14px - height: 14px - display: inline-block - vertical-align: middle - -.icon-death - background: url("/bower_components/habitrpg-shared/img/sprites/dead.png") no-repeat - background-position: center center - background-size: 14px - width: 14px - height: 14px - display: inline-block - vertical-align: middle - .undo-button z-index: 3000 position:absolute diff --git a/public/css/index.styl b/public/css/index.styl index 6d00b3e041..c10b3b12c9 100644 --- a/public/css/index.styl +++ b/public/css/index.styl @@ -118,6 +118,10 @@ html, body float: left padding: 15px + &.herobox + padding-right: 0px + padding-left: 0px + .death-modal // a .modal-body margin: 10px @@ -163,3 +167,19 @@ a.label background-color: #428bca .badge-success background-color: #47a447 + +.white, .white a + color: #fff !important + +.collapse-toolbar + position:absolute + right:0px + top:0px + width:30px + height:25px + background-color:#222 + z-index:999 + .glyphicon + color:white + padding-left:7px + padding-top:5px diff --git a/public/css/npcs.styl b/public/css/npcs.styl index 48493536b3..4f55489d5c 100644 --- a/public/css/npcs.styl +++ b/public/css/npcs.styl @@ -4,15 +4,15 @@ .npc_matt margin-bottom: 20px +.toolbar-bailey-container + width:55px + position:relative .npc_bailey float:left .npc_bailey_head - //height: 30px !important - height: 50px !important position: absolute - //top: 117px // 147 (header-height) - 30 (bailey height) - top: 97px - right: 50px + top: 4px + height:45px !important cursor: pointer // Tour (Justin) diff --git a/public/js/app.js b/public/js/app.js index 57fb7a25f3..ef55bb10d4 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -114,8 +114,12 @@ window.habitrpg = angular.module('habitrpg', .state('options.social.guilds.detail', { url: '/:gid', templateUrl: 'partials/options.social.guilds.detail.html', - controller: ['$scope', 'Groups', '$stateParams', function($scope, Groups, $stateParams){ - $scope.group = Groups.Group.get({gid:$stateParams.gid}); + controller: ['$scope', 'Groups', '$stateParams', + function($scope, Groups, $stateParams){ + Groups.Group.get({gid:$stateParams.gid}, function(group){ + $scope.group = group; + Groups.seenMessage(group._id); + }); }] }) diff --git a/public/js/controllers/groupsCtrl.js b/public/js/controllers/groupsCtrl.js index 1c5bd00958..c0ba02c5a1 100644 --- a/public/js/controllers/groupsCtrl.js +++ b/public/js/controllers/groupsCtrl.js @@ -319,6 +319,9 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.text = 'Party'; $scope.group = $rootScope.party = Groups.party(); $scope.newGroup = new Groups.Group({type:'party'}); + + Groups.seenMessage($scope.group._id); + $scope.create = function(group){ group.$save(function(newGroup){ $scope.group = newGroup; diff --git a/public/js/controllers/notificationCtrl.js b/public/js/controllers/notificationCtrl.js index 8633c16fc5..82c42af8a7 100644 --- a/public/js/controllers/notificationCtrl.js +++ b/public/js/controllers/notificationCtrl.js @@ -94,7 +94,6 @@ habitrpg.controller('NotificationCtrl', $rootScope.$watch('user.items.' + watched, function(before, after){ if (after == before) return; if (+after < +before) { - Notification.death(); //don't want to day "lost a head" if (watched === 'head') watched = 'helm'; Notification.text('Lost GP, 1 LVL, ' + watched); @@ -116,12 +115,6 @@ habitrpg.controller('NotificationCtrl', } }); - // Math updates modal - $rootScope.$watch('!user.flags.mathUpdates', function(after, before){ - if (after == before || after != true) return; - $rootScope.openModal('mathUpdates'); - }); - // Completed quest modal $rootScope.$watch('user.party.quest.completed', function(after, before){ if (after == before || after != true) return; diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index d9d56efe9e..508afbdf33 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -26,6 +26,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ $rootScope.Shared = Shared; $rootScope.Content = Content; $rootScope.env = window.env; + $rootScope.Math = Math; // Angular UI Router $rootScope.$state = $state; @@ -198,6 +199,11 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ */ $scope.castStart = function(spell) { if (User.user.stats.mp < spell.mana) return Notification.text(window.env.t('notEnoughMana')); + + // Temporary for valentine's day, remove after event + if (spell.key == 'valentine' && User.user.stats.gp < spell.value) + return Notification.text('Not enough gold.'); + $rootScope.applyingAction = true; $scope.spell = spell; if (spell.target == 'self') { diff --git a/public/js/services/groupServices.js b/public/js/services/groupServices.js index 1f72b4a648..db33d73de9 100644 --- a/public/js/services/groupServices.js +++ b/public/js/services/groupServices.js @@ -5,8 +5,8 @@ */ angular.module('groupServices', ['ngResource']). - factory('Groups', ['API_URL', '$resource', '$q', - function(API_URL, $resource, $q) { + factory('Groups', ['API_URL', '$resource', '$q', '$http', 'User', + function(API_URL, $resource, $q, $http, User) { var Group = $resource(API_URL + '/api/v2/groups/:gid', {gid:'@_id', messageId: '@_messageId'}, { @@ -44,6 +44,12 @@ angular.module('groupServices', ['ngResource']). return tavern; }, + // On enter, set chat message to "seen" + seenMessage: function(gid){ + $http.post('/api/v2/groups/'+gid+'/chat/seen'); + if (User.user.newMessages) User.user.newMessages[gid] = false; + }, + Group: Group } } diff --git a/public/js/services/notificationServices.js b/public/js/services/notificationServices.js index 02f92bbc83..c67770e8f0 100644 --- a/public/js/services/notificationServices.js +++ b/public/js/services/notificationServices.js @@ -25,11 +25,11 @@ angular.module("notificationServices", []) gold = Math.floor(absolute); silver = Math.floor((absolute - gold) * 100); if (gold && silver > 0) { - return "" + gold + " " + silver + " "; + return "" + gold + " " + silver + " "; } else if (gold > 0) { - return "" + gold + " "; + return "" + gold + " "; } else if (silver > 0) { - return "" + silver + " "; + return "" + silver + " "; } }; @@ -60,9 +60,6 @@ angular.module("notificationServices", []) lvl: function(){ growl(' ' + window.env.t('levelUp'), 'lvl'); }, - death: function(){ - growl(" " + window.env.t('respawn'), "death"); - }, error: function(error){ growl("  " + error, "danger"); }, diff --git a/public/manifest.json b/public/manifest.json index ce7e2b6e8c..85e39cd970 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "app": { "js": [ - "bower_components/jquery/jquery.js", + "bower_components/jquery/dist/jquery.min.js", "bower_components/jquery.cookie/jquery.cookie.js", "bower_components/bootstrap-growl/jquery.bootstrap-growl.js", "bower_components/bootstrap-tour/build/js/bootstrap-tour.js", @@ -72,7 +72,7 @@ }, "static": { "js": [ - "bower_components/jquery/jquery.js", + "bower_components/jquery/dist/jquery.min.js", "bower_components/habitrpg-shared/dist/habitrpg-shared.js", "bower_components/angular/angular.js", "bower_components/angular-ui/build/angular-ui.js", diff --git a/src/controllers/groups.js b/src/controllers/groups.js index cfdaa2d179..4bdde2b83a 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -249,6 +249,14 @@ api.deleteChatMessage = function(req, res){ }); } +api.seenMessage = function(req,res,next){ + // Skip the auth step, we want this to be fast. If !found with uuid/token, then it just doesn't save + var update = {$set:{}}; + update['$set']['newMessages.'+req.params.gid+'.value'] = false; + User.update({_id:req.headers['x-api-user'], apiToken:req.headers['x-api-key']},update).exec(); + res.send(200); +} + api.likeChatMessage = function(req, res, next) { var user = res.locals.user; var group = res.locals.group; diff --git a/src/controllers/user.js b/src/controllers/user.js index 5556a25093..b8979639c4 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -89,10 +89,25 @@ api.score = function(req, res, next) { delta: delta, _tmp: user._tmp }, saved.toJSON().stats)); - }); - // if it's a challenge task, sync the score - user.syncScoreToChallenge(task, delta); + // If it's a challenge task, sync the score. Do it in the background, we've already sent down a response + // and the user doesn't care what happens back there + if (!task.challenge || !task.challenge.id || task.challenge.broken) return; + if (task.type == 'reward') return; // we don't want to update the reward GP cost + Challenge.findById(task.challenge.id, 'habits dailys todos rewards', function(err, chal){ + if (err) return next(err); + if (!chal) { + task.challenge.broken = 'CHALLENGE_DELETED'; + return user.save(); + } + var t = chal.tasks[task.id]; + if (!t) return chal.syncToUser(user); // this task was removed from the challenge, notify user + t.value += delta; + if (t.type == 'habit' || t.type == 'daily') + t.history.push({value: t.value, date: +new Date}); + chal.save(); + }); + }); }; /** @@ -409,7 +424,7 @@ api.cast = function(req, res, next) { case 'user': async.waterfall([ function(cb){ - Group.findOne({type: 'party', members: {'$in': [user._id]}}).populate('members', 'profile.name stats achievements').exec(cb); + Group.findOne({type: 'party', members: {'$in': [user._id]}}).populate('members', 'profile.name stats achievements items.special').exec(cb); }, function(group, cb) { // Solo player? let's just create a faux group for simpler code diff --git a/src/middleware.js b/src/middleware.js index 30e95ca5c6..4894b586d1 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -122,22 +122,21 @@ var getManifestFiles = function(page){ if(!files) throw new Error("Page not found!"); - var css = ''; - - _.each(files.css, function(file){ - css += ''; - }); + var code = ''; if(nconf.get('NODE_ENV') === 'production'){ - return css + ''; + code += ''; + code += ''; }else{ - var results = css; - _.each(files.js, function(file){ - results += ''; + _.each(files.css, function(file){ + code += ''; + }); + _.each(files.js, function(file){ + code += ''; }); - return results; } - + + return code; } // Translations diff --git a/src/models/group.js b/src/models/group.js index f3b3cad8e5..5d80c0baf4 100644 --- a/src/models/group.js +++ b/src/models/group.js @@ -106,6 +106,17 @@ GroupSchema.methods.sendChat = function(message, user){ } group.chat.unshift(message); group.chat.splice(200); + + // Kick off chat notifications in the background. + var lastSeenUpdate = {$set:{}, $inc:{_v:1}}; + lastSeenUpdate['$set']['newMessages.'+group._id] = {name:group.name,value:true}; + if (group._id == 'habitrpg') { + // TODO For Tavern, only notify them if their name was mentioned + // var profileNames = [] // get usernames from regex of @xyz. how to handle space-delimited profile names? + // User.update({'profile.name':{$in:profileNames}},lastSeenUpdate,{multi:true}).exec(); + } else { + mongoose.model('User').update({_id:{$in:group.members, $ne: user ? user._id : ''}},lastSeenUpdate,{multi:true}).exec(); + } } var cleanQuestProgress = function(merge){ @@ -164,13 +175,13 @@ GroupSchema.methods.finishQuest = function(quest, cb) { } // FIXME this is a temporary measure, we need to remove quests from users when they traverse parties -function isOnQuest(user,group){ - return group && user.party.quest.key && user.party.quest.key == group.quest.key; +function isOnQuest(user,progress,group){ + return group && progress && user.party.quest.key && user.party.quest.key == group.quest.key; } GroupSchema.statics.collectQuest = function(user, progress, cb) { this.findOne({type: 'party', members: {'$in': [user._id]}},function(err, group){ - if (!isOnQuest(user,group)) return cb(null); + if (!isOnQuest(user,progress,group)) return cb(null); var quest = shared.content.quests[group.quest.key]; _.each(progress.collect,function(v,k){ @@ -204,7 +215,7 @@ GroupSchema.statics.collectQuest = function(user, progress, cb) { GroupSchema.statics.bossQuest = function(user, progress, cb) { this.findOne({type: 'party', members: {'$in': [user._id]}},function(err, group){ - if (!isOnQuest(user,group)) return cb(null); + if (!isOnQuest(user,progress,group)) return cb(null); var quest = shared.content.quests[group.quest.key]; if (!progress || !quest) return cb(null); // FIXME why is this ever happening, progress should be defined at this point var down = progress.down * quest.boss.str; // multiply by boss strength diff --git a/src/models/user.js b/src/models/user.js index 2d81658e0f..9c1c5ba0cc 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -42,7 +42,8 @@ var UserSchema = new Schema({ rebirths: Number, rebirthLevel: Number, perfect: Number, - habitBirthday: Boolean + habitBirthday: Boolean, + valentine: Number }, auth: { facebook: Schema.Types.Mixed, @@ -136,7 +137,9 @@ var UserSchema = new Schema({ }, special:{ - snowball: {type: Number, 'default': 0} + snowball: {type: Number, 'default': 0}, + valentine: Number, + valentineReceived: Array // array of strings, by sender name }, // -------------- Animals ------------------- @@ -207,9 +210,11 @@ var UserSchema = new Schema({ lastCron: {type: Date, 'default': Date.now}, + // {GROUP_ID: Boolean}, represents whether they have unseen chat messages + newMessages: {type: Schema.Types.Mixed, 'default': {}}, + party: { // id // FIXME can we use a populated doc instead of fetching party separate from user? - lastMessageSeen: String, order: {type:String, 'default':'level'}, quest: { key: String, @@ -245,7 +250,8 @@ var UserSchema = new Schema({ disableClasses: {type: Boolean, 'default': false}, newTaskEdit: {type: Boolean, 'default': false}, tagsCollapsed: {type: Boolean, 'default': false}, - advancedCollapsed: {type: Boolean, 'default': false} + advancedCollapsed: {type: Boolean, 'default': false}, + toolbarCollapsed: {type:Boolean, 'default':false} }, profile: { blurb: String, @@ -367,22 +373,6 @@ UserSchema.pre('save', function(next) { next(); }); -UserSchema.methods.syncScoreToChallenge = function(task, delta){ - if (!task.challenge || !task.challenge.id || task.challenge.broken) return; - if (task.type == 'reward') return; // we don't want to update the reward GP cost - var self = this; - Challenge.findById(task.challenge.id, function(err, chal){ - if (err) throw err; - var t = chal.tasks[task.id]; - if (!t) return chal.syncToUser(self); // this task was removed from the challenge, notify user - t.value += delta; - if (t.type == 'habit' || t.type == 'daily') { - t.history.push({value: t.value, date: +new Date}); - } - chal.save(); - }); -} - UserSchema.methods.unlink = function(options, cb) { var cid = options.cid, keep = options.keep, tid = options.tid; var self = this; diff --git a/src/routes/apiv2.coffee b/src/routes/apiv2.coffee index 513fb7e312..db5a0010f0 100644 --- a/src/routes/apiv2.coffee +++ b/src/routes/apiv2.coffee @@ -488,6 +488,17 @@ module.exports = (swagger, v2) -> middleware: [auth.auth, groups.attachGroup] action: groups.postChat + # placing before route below, so that if !=='seen' it goes to next() + "/groups/{gid}/chat/seen": + spec: + method: 'POST' + description: "Flag chat messages for a particular group as seen" + parameters: [ + path 'gid','Group id','string' + ] + middleware: [] + action: groups.seenMessage + "/groups/{gid}/chat/{messageId}": spec: method: 'DELETE' diff --git a/src/server.js b/src/server.js index 6cefab8db0..bef7642207 100644 --- a/src/server.js +++ b/src/server.js @@ -116,7 +116,9 @@ if (cluster.isMaster && (isDev || isProd)) { app.use(app.router); var maxAge = isProd ? 31536000000 : 0; + // Cache emojis without copying them to build, they are too many app.use(express['static'](path.join(__dirname, "/../build"), { maxAge: maxAge })); + app.use('/bower_components/habitrpg-shared/img/emoji/unicode', express['static'](path.join(__dirname, "/../public/bower_components/habitrpg-shared/img/emoji/unicode"), { maxAge: maxAge })); app.use(express['static'](path.join(__dirname, "/../public"))); // Custom Directives diff --git a/test/api.mocha.js b/test/api.mocha.js index 9b69fd461e..3f4f20d6f2 100644 --- a/test/api.mocha.js +++ b/test/api.mocha.js @@ -253,6 +253,33 @@ describe('API', function () { }); }); + it('Challenge deleted, breaks task link', function (done) { + var itThis = this; + request.del(baseURL + "/challenges/" + challenge._id).end(function (res) { + User.findById(user._id, function(err,user){ + var len = user.dailys.length- 1, + daily = user.dailys[user.dailys.length-1]; + expect(daily.challenge.broken).to.be('CHALLENGE_DELETED'); + + // Now let's handle if challenge was deleted, but didn't get to update all the users (an error) + var unset = {$unset:{}}; + unset['$unset']['dailys.' + len + '.challenge.broken'] = 1; + User.findByIdAndUpdate(user._id,unset,function(err,user){ + expect(err).to.not.be.ok(); + expect(user.dailys[len].challenge.broken).to.not.be.ok(); + request.post(baseURL + "/user/tasks/" + daily.id + "/up").end(function (res) { + setTimeout(function(){ + User.findById(user._id,function(err,user){ + expect(user.dailys[len].challenge.broken).to.be('CHALLENGE_DELETED'); + done() + }) + }, 100); // we need to wait for challenge to update user, it's a background job for perf reasons + }) + }) + }) + }) + }); + it('Admin creates a challenge', function (done) { User.findByIdAndUpdate(_id, {$set:{'contributor.admin':true}}, function (err,_user) { expect(err).to.not.be.ok(); diff --git a/views/index.jade b/views/index.jade index aedb605b44..85f6c5d642 100644 --- a/views/index.jade +++ b/views/index.jade @@ -20,19 +20,15 @@ html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":a body(ng-cloak, ng-controller='GroupsCtrl') + include ./shared/header/menu include ./shared/modals/index include ./shared/header/header - include ./shared/tasks/lists include ./main/index include ./options/index #notification-area(ng-controller='NotificationCtrl') #wrap.container-fluid - //if they hide the header, we still need user-menu visible - div(ng-if='user.preferences.hideHeader') - include ./shared/header/menu - .row .col-md-12.exp-chart(ng-show='charts.exp') #main(ui-view) diff --git a/views/main/filters.jade b/views/main/filters.jade index 923d61e2a6..e356e552e5 100644 --- a/views/main/filters.jade +++ b/views/main/filters.jade @@ -21,7 +21,7 @@ form.form-inline(ng-show='_editing', ng-submit='createTag(_newTag)') .input-group input.form-control.input-sm(type='text', ng-model='_newTag', placeholder=env.t('newTag')) - span.input-group-addon(type='submit') + span.input-group-addon(ng-click='createTag(_newTag)') =env.t('add') //
  • li diff --git a/views/options/inventory/inventory.jade b/views/options/inventory/inventory.jade index 86171c082d..94264834df 100644 --- a/views/options/inventory/inventory.jade +++ b/views/options/inventory/inventory.jade @@ -68,6 +68,12 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html') button.customize-option(popover='{{Content.special.snowball.notes}}', popover-title='{{Content.special.snowball.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='castStart(Content.special.snowball)', class='inventory_special_snowball') .badge.badge-info.stack-count {{user.items.special.snowball}} + li.customize-menu(ng-if='user.items.special.valentineReceived[0]') + menu.pets-menu(label=env.t('valentineCard')) + div + button.customize-option(popover="Valentine's Day Card from {{User.user.items.special.valentineReceived[0]}}", popover-trigger='mouseenter', popover-placement='right', ng-click='openModal("valentine")', class='inventory_special_valentine') + .badge.badge-info.stack-count {{user.items.special.valentineReceived.length}} + li.customize-menu(ng-if='user.purchased.plan.customerId') menu.pets-menu(label=env.t('subscriberItem')) div diff --git a/views/options/profile.jade b/views/options/profile.jade index c9dc5b4575..18c0d72ef8 100644 --- a/views/options/profile.jade +++ b/views/options/profile.jade @@ -3,7 +3,7 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template') .col-md-4 h3=env.t('bodyBody') small - | 2 / + | 2 / = ' ' + env.t('locked') h5=env.t('bodySize') @@ -27,7 +27,7 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template') .col-md-4 h3=env.t('bodyHead') small - | 2 / + | 2 / = ' ' + env.t('locked') menu(type='list') // For special events code, see commit dfa27b3 @@ -86,7 +86,7 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template') .col-md-4 h3=env.t('bodySkin') small - | 2 / + | 2 / = ' ' + env.t('locked') // skin li.customize-menu diff --git a/views/options/social/challenge-box.jade b/views/options/social/challenge-box.jade index ff7cb21926..758a28ece3 100644 --- a/views/options/social/challenge-box.jade +++ b/views/options/social/challenge-box.jade @@ -14,8 +14,8 @@ p |  =env.t('noChallenges') + |  a.label.label-primary(ui-sref='options.social.challenges') - |  span.glyphicon.glyphicon-bullhorn |  =env.t('challenges') diff --git a/views/options/social/create-group.jade b/views/options/social/create-group.jade index 53c8879a02..bd2563cd76 100644 --- a/views/options/social/create-group.jade +++ b/views/options/social/create-group.jade @@ -5,7 +5,7 @@ label.control-label(for='new-group-name') | {{text}}  =env.t('name') - input.form-control#new-group-name.input-medium.option-content(required, type='text', placeholder='{{text}} Name', ng-model='newGroup.name') + input.form-control#new-group-name.input-medium.option-content(required, type='text', placeholder=('{{text}} ' + env.t('name')), ng-model='newGroup.name') .form-group label(for='new-group-description')=env.t('description') textarea.form-control#new-group-description.option-content(cols='3', placeholder=env.t('description'), ng-model='newGroup.description') diff --git a/views/options/social/index.jade b/views/options/social/index.jade index a47ca1331a..cc2d7971c1 100644 --- a/views/options/social/index.jade +++ b/views/options/social/index.jade @@ -12,7 +12,7 @@ script(type='text/ng-template', id='partials/options.social.party.html') include ./group div(ng-hide='group._id') div(ng-show='user.invitations.party.id') - h2=env.t('invitedTo') + ' {{user.invitations.party.name}}' + h2=env.t('invitedTo', {name: '{{user.invitations.party.name}}'}) a.btn.btn-success(data-type='party', ng-click='join(user.invitations.party)')=env.t('accept') a.btn.btn-danger(ng-click='reject()')=env.t('reject') div(ng-hide='user.invitations.party.id') @@ -25,7 +25,7 @@ script(type='text/ng-template', id='partials/options.social.party.html') script(type='text/ng-template', id='partials/options.social.guilds.public.html') div(ng-repeat='invitation in user.invitations.guilds') - h3=env.t('invitedTo') + ' {{invitation.name}}' + h3=env.t('invitedTo', {name: '{{invitation.name}}'}) a.btn.btn-success(data-type='guild', ng-click='join(invitation)')=env.t('accept') a.btn.btn-danger(ng-click='reject(invitation)')=env.t('reject') // Public Groups diff --git a/views/shared/header/avatar.jade b/views/shared/header/avatar.jade index abc1c2fff8..971560d5af 100644 --- a/views/shared/header/avatar.jade +++ b/views/shared/header/avatar.jade @@ -48,4 +48,5 @@ figure.herobox(ng-click='spell ? castEnd(profile, "user", $event) : clickMember( |  =env.t('lvl') | {{profile.stats.lvl}} + |  span.glyphicon.glyphicon-plus-sign(ng-show='profile.achievements.rebirths', tooltip=env.t('reborn', {reLevel: "{{profile.achievements.rebirthLevel}}"})) diff --git a/views/shared/header/header.jade b/views/shared/header/header.jade index d62920ab4c..6481d1a5e6 100644 --- a/views/shared/header/header.jade +++ b/views/shared/header/header.jade @@ -1,7 +1,5 @@ .header-wrap(ng-controller='HeaderCtrl') a.label.label-default.undo-button(x-bind='click:undo', ng-show='_undo')=env.t('undo') - div(ng-if='!user.preferences.hideHeader') - include menu header.site-header(ng-hide='user.preferences.hideHeader', role='banner', data-partysize='{{party.members.length>1 ? truarr(party.members.length) : 0}}') // avatar @@ -38,6 +36,3 @@ span(ng-controller='PartyCtrl') .herobox-wrap(ng-repeat='profile in partyMinusSelf') include avatar - - .npc_bailey.npc_bailey_head(ng-show='user.flags.newStuff', tooltip=env.t('psst'), tooltip-placement='top', ng-click='openModal("newStuff")') - diff --git a/views/shared/header/menu.jade b/views/shared/header/menu.jade index eea952c974..6e0bb09ac1 100644 --- a/views/shared/header/menu.jade +++ b/views/shared/header/menu.jade @@ -1,41 +1,124 @@ -.user-menu(ng-controller='AuthCtrl') - ul.nav.site-nav(ng-show='authenticated()') - li.flyout - h1.task-action-btn.tile.solid.user-reporter {{user.profile.name}} - ul.flyout-content.nav.stacked - li - a.task-action-btn.tile.solid - span(ng-show='$state.includes("options")', ui-sref='tasks') - span.glyphicon.glyphicon-ok +a.collapse-toolbar(popover=env.t('expandToolbar'),popover-placement='left',popover-trigger='mouseenter',ng-if='user.preferences.toolbarCollapsed',ng-click='set({"preferences.toolbarCollapsed":false})') + span.glyphicon.glyphicon-chevron-down + +nav.navbar.navbar-inverse(ng-controller='AuthCtrl',style='min-height:0px;margin-bottom:0px;border:0;border-radius:0px',ng-if='!user.preferences.toolbarCollapsed') + + ul.nav.navbar-nav.pull-right + li + a(popover=env.t('collapseToolbar'),popover-placement='left',popover-trigger='mouseenter',,ng-if='!user.preferences.toolbarCollapsed',ng-click='set({"preferences.toolbarCollapsed":true})') + span.glyphicon.glyphicon-chevron-up.white + + .navbar-header + button.navbar-toggle(type='button', ng-click='isNavbarCollapsed = !isNavbarCollapsed', ng-init='isNavbarCollapsed = true') + span.sr-only Toggle navigation + span.icon-bar + span.icon-bar + span.icon-bar + .collapse.navbar-collapse(collapse="isNavbarCollapsed") + ul.nav.navbar-nav + li + a(ui-sref='tasks',popover=env.t('tasks'),popover-placement='right',popover-trigger='mouseenter') + span.glyphicon.glyphicon-check.white + li.dropdown + a.dropdown-toggle(popover='User',popover-placement='right',popover-trigger='mouseenter') + span.glyphicon.glyphicon-user.white + span.caret + ul.dropdown-menu + li + a(ui-sref='options.profile.avatar')=env.t('avatar') + li + a(ui-sref='options.profile.stats')=env.t('stats') + li + a(ui-sref='options.profile.profile')=env.t('profile') + li + a(ng-click='logout()') + span.glyphicon.glyphicon-share-alt |   - =env.t('tasks') - span(ng-show='$state.includes("tasks")', ui-sref='options') - span.glyphicon.glyphicon-wrench - |   - =env.t('options') - li - a.task-action-btn.tile.solid(href="http://habitrpg.wikia.com/wiki/FAQ", target='_blank') - span.glyphicon.glyphicon-book - |   - =env.t('FAQ') - li - a.task-action-btn.tile.solid(href="https://vimeo.com/57654086", target='_blank') - span.glyphicon.glyphicon-film - //|   Tutorials is too long to stay in the menu if we add a space - =env.t('tutorials') - li - a.task-action-btn.tile.solid(ng-click='User.sync()') - span.glyphicon.glyphicon-refresh - |   - =env.t('sync') - li - a.task-action-btn.tile.solid(ng-click='logout()') - span.glyphicon.glyphicon-share-alt - |   - =env.t('logout') - // party invitation notification - a(ng-show='user.party.invitation', x-bind='click:gotoPartyChat') - span.glyphicon.glyphicon-user - // party chat notification - a(ng-show='newChatMessages(_party.chat,user.party.lastMessageSeen)', x-bind='click:gotoPartyChat') - span.glyphicon.glyphicon-comment(tooltip=env.t('partyNotification')) + =env.t('logout') + li.dropdown + a.dropdown-toggle(popover=env.t('social'),popover-placement='right',popover-trigger='mouseenter') + span.glyphicon.glyphicon-heart.white + span.caret + ul.dropdown-menu + li + a(ui-sref='options.social.tavern')=env.t('tavern') + li + a(ui-sref='options.social.party')=env.t('party') + li + a(ui-sref='options.social.guilds')=env.t('guilds') + li + a(ui-sref='options.social.challenges')=env.t('challenges') + li + a(ui-sref='options.social.hall')=env.t('hall') + li.dropdown + a.dropdown-toggle(popover=env.t('inventory'), popover-placement='right',popover-trigger='mouseenter') + span.glyphicon.glyphicon-gift.white + span.caret + ul.dropdown-menu + li + a(ui-sref='options.inventory.drops')=env.t('market') + li + a(ui-sref='options.inventory.pets')=env.t('pets') + li + a(ui-sref='options.inventory.mounts')=env.t('mounts') + li + a(ui-sref='options.inventory.equipment')=env.t('equipment') + li.dropdown + a.dropdown-toggle(popover=env.t('settings'),popover-placement='right',popover-trigger='mouseenter') + span.glyphicon.glyphicon-wrench.white + span.caret + ul.dropdown-menu + li + a(ui-sref='options.settings.settings')=env.t('site') + li + a(ui-sref='options.settings.api')=env.t('API') + li + a(ui-sref='options.settings.export')=env.t('export') + li + a(ui-sref='options.settings.subscription')=env.t('subscription') + li.dropdown + a.dropdown-toggle(popover=env.t('help'),popover-placement='right',popover-trigger='mouseenter') + span.glyphicon.glyphicon-question-sign.white + span.caret + ul.dropdown-menu + li + a(href="http://habitrpg.wikia.com/wiki/FAQ", target='_blank') + span.glyphicon.glyphicon-book + |  + =env.t('FAQ') + li + a(href="https://vimeo.com/57654086", target='_blank') + span.glyphicon.glyphicon-film + |  + =env.t('tutorials') + li + a(ng-click='User.sync()',popover=env.t('sync'),popover-placement='right',popover-trigger='mouseenter') + span.glyphicon.glyphicon-refresh.white + li + a.pull-right.gem-wallet(style='padding-top:8px;padding-bottom:0px;',ng-click='openModal("buyGems",{track:"Gems > Toolbar"})', popover-trigger='mouseenter', popover-title=env.t('gems'), popover=env.t('gemsWhatFor'), popover-placement='bottom',popover-append-to-body='true') + //span.task-action-btn.tile.flush.bright.add-gems-btn + + span.task-action-btn.tile.flush.neutral + .Pet_Currency_Gem2x.Gems + | {{user.balance * 4 | number:0}} + li + button.btn.btn-primary.navbar-btn(ng-if='!user.purchased.plan.customerId',ui-sref='options.settings.subscription',popover-trigger='mouseenter',popover-placement='bottom',popover-title=env.t('subscriptions'),popover=env.t('subDescription'),popover-append-to-body='true')=env.t('subscribe') + button.btn.btn-default.navbar-btn(ng-if='user.purchased.plan.customerId',ui-sref='options.settings.subscription',popover-trigger='mouseenter',popover-placement='bottom',popover=env.t('manageSub'),popover-append-to-body='true')=env.t('subscribed') + + li(ng-if='user.flags.newStuff') + div.toolbar-bailey-container + |  + .npc_bailey.npc_bailey_head(popover=env.t('psst'), popover-trigger='mouseenter', popover-placement='right', ng-click='openModal("newStuff")') + + // Invitations + li(ng-if='user.invitations.party.id') + a(ui-sref='options.social.party') + span.glyphicon.glyphicon-user.white(popover=env.t('invitedTo', {name: '{{user.invitations.party.name}}'}),popover-placement='bottom',popover-trigger='mouseenter',popover-append-to-body='true') + li(ng-repeat='guild in user.invitations.guilds') + a(ui-sref='options.social.guilds') + span.glyphicon.glyphicon-user.white(popover=env.t('invitedTo', {name: '{{guild.name}}'}),popover-placement='bottom',popover-trigger='mouseenter',popover-append-to-body='true') + // Chat notifs + li(ng-repeat='(k,v) in user.newMessages', ng-if='v.value') + a(ng-click='k==party._id ? $state.go("options.social.party") : $state.go("options.social.guilds.detail",{gid:k})') + span.glyphicon.glyphicon-comment.white(popover='New message in "{{v.name}}"',popover-placement='bottom',popover-trigger='mouseenter',popover-append-to-body='true') + + diff --git a/views/shared/modals/index.jade b/views/shared/modals/index.jade index a8c0626e99..c16838f72e 100644 --- a/views/shared/modals/index.jade +++ b/views/shared/modals/index.jade @@ -8,4 +8,5 @@ include ./settings include ./drops include ./classes include ./quests -include ./rebirth \ No newline at end of file +include ./rebirth +include ./limited \ No newline at end of file diff --git a/views/shared/modals/limited.jade b/views/shared/modals/limited.jade new file mode 100644 index 0000000000..8d2d31ce90 --- /dev/null +++ b/views/shared/modals/limited.jade @@ -0,0 +1,30 @@ +//Valentine +script(id='modals/valentine.html', type='text/ng-template') + .modal-header + h4 + .inventory_special_valentine.pull-right + =env.t('valentineCard') + .modal-body + .bg-info(style='padding:10px') + p To: {{user.profile.name}}, From: {{user.items.special.valentineReceived[0]}} + hr + div(ng-switch='Math.floor(Math.random()*3)') + ul.list-unstyled(ng-switch-when='0') + li "Roses are red + li My Dailies are blue + li I'm happy that I'm + li In a Party with you!" + ul.list-unstyled(ng-switch-when='1') + li "Roses are red + li Violets are nice + li Let's get together + li And fight against Vice!" + ul.list-unstyled(ng-switch-default) + li "Roses are red + li This poem style is old + li I hope that you like this + li Cuz it cost ten gold." + p + small For enduring such a saccharine poem, you both receive the "Adoring Friends" badge! + .modal-footer + button.btn.btn-default(ng-click='user.ops.readValentine({});$close()')=env.t('ok') \ No newline at end of file diff --git a/views/shared/modals/new-stuff.jade b/views/shared/modals/new-stuff.jade index ebdf889759..4e9d44abe9 100644 --- a/views/shared/modals/new-stuff.jade +++ b/views/shared/modals/new-stuff.jade @@ -1,17 +1,3 @@ -//-div(modal='!user.flags.mathUpdates') -script(type='text/ng-template', id='modals/mathUpdates.html') - .modal-header - h4 Math Updates - .modal-body - .npc_tyler.pull-left - p Hey all, Tyler here. Sorry for the rocky numbers (Exp, GP, etc) with the Class System roll-out! We're tweaking the math today, and things should be mostly finalized by tomorrow. Follow our progress here, and please do chime in! - br - p We've also changed classes to unlock at lvl 10 instead of 5, to prevent overwhelming our new users. And since users have expressed confusion, we'll be improving UI and clarifification. Stay tuned. - br - p iOS users, iTunes is on vacation until 12/28. Until we can push the updated app, you'll need to sync periodically to get the corrected numbers (app has old math, server saves the correct numbers). Android goes out ASAP. More info on Trello. - .modal-footer - button.btn.btn-default(ng-click='User.set({"flags.mathUpdates":true}); $close()') Dismiss - script(type='text/ng-template', id='modals/newStuff.html') .modal-header h4 New Stuff! @@ -26,37 +12,58 @@ script(type='text/ng-template', id='modals/newStuff.html') h3.popover-title a(target='_blank', href='https://twitter.com/Mihakuu') Bailey .popover-content - table.table.table-striped - tr - td - h5 February Mystery Item - p - .pull-right.inventory_present - | We're excited to announce a new feature a s a big thank-you to the awesome people who subscribe to HabitRPG! Every month, all subscribers will now receive a limited-edition mystery item! The mystery item will be a stats-free costume piece (like the Absurd Party Robes) that will only be available to the people who are subscribers each month. The February 2014 item will be revealed on the 23rd to everyone, but all people who are subscribers during the month of February will receive it. Subscribe now, get excited, and thank you so much for helping to support HabitRPG! We love you. - tr - td - h5 Critical Hammer Of Bug-Crushing - p - .pull-right.weapon_special_critical - | Some of you may have noticed that we periodically have some bugs that are nastier than the norm - the dreaded critical bugs. These monstrous apparitions have been snapping at the heels of many a player. For updates on what we're currently working on to improve site stability, read this link - and then jump in to help! Not only will programming assistance reward you with the usual contributor levels, but if you actually manage to fix a bug marked "critical," you will now receive the Critical Hammer of Bug-Crushing as your reward! - tr - td - - h5 Rainbow Hair Colors - p - .pull-right.customize-option.hair_bangs_1_rainbow - | Want to spruce up your avatar? Rainbow hair colors are now available! Dye your luscious locks purple, green, or even rainbow-striped, and passersby will look at you with envy. - tr - td - h5 Stability Update - p We've stabilized the site a lot (we're still working out kinks, but we're way better now). Follow the progress here, but here are some workarounds for now: - ul - li Click slower. VersionError is caused by clicking things off too fast (we're working on a fix). - li If you see an error, refresh before proceeding. + h5 Happy Valentine's Day! + p Help motivate all of the lovely people in your life by sending them a caring valentine. Valentines can be purchased for 10 gold from the Item Store. For spreading love and joy throughout the community, both the giver AND the receiver get a coveted "adoring friends" badge. Hooray! p - small.muted By Lemoness, mariahm, crystalphoenix, aiseant, zoebeagle, cole, lefnire + small.muted By Lemoness and zoebeagle + hr + p + small.muted 02/13/2014 + hr + h5 02/12/2014 + table.table.table-striped + tr + td + h5 Chat & Invite Notifications + p Chat & group-invitation notifications are back! Miss them? They currently work for all chat updates in parties & guilds. Any devs willing to jump into @tagging in Tavern, see here. + tr + td + h5 Toolbar + p In order to make room for these notifs, we added a toolbar above the header. You can collapse the toolbar (far-right icon), but take care as Bailey notifs are inside the toolbar! + hr + h5 02/07/2014 + table.table.table-striped + tr + td + h5 February Mystery Item + p + .pull-right.inventory_present + | We're excited to announce a new feature a s a big thank-you to the awesome people who subscribe to HabitRPG! Every month, all subscribers will now receive a limited-edition mystery item! The mystery item will be a stats-free costume piece (like the Absurd Party Robes) that will only be available to the people who are subscribers each month. The February 2014 item will be revealed on the 23rd to everyone, but all people who are subscribers during the month of February will receive it. Subscribe now, get excited, and thank you so much for helping to support HabitRPG! We love you. + tr + td + h5 Critical Hammer Of Bug-Crushing + p + .pull-right.weapon_special_critical + | Some of you may have noticed that we periodically have some bugs that are nastier than the norm - the dreaded critical bugs. These monstrous apparitions have been snapping at the heels of many a player. For updates on what we're currently working on to improve site stability, read this link - and then jump in to help! Not only will programming assistance reward you with the usual contributor levels, but if you actually manage to fix a bug marked "critical," you will now receive the Critical Hammer of Bug-Crushing as your reward! + tr + td + + h5 Rainbow Hair Colors + p + .pull-right.customize-option.hair_bangs_1_rainbow + | Want to spruce up your avatar? Rainbow hair colors are now available! Dye your luscious locks purple, green, or even rainbow-striped, and passersby will look at you with envy. + tr + td + h5 Stability Update + p We've stabilized the site a lot (we're still working out kinks, but we're way better now). Follow the progress here, but here are some workarounds for now: + ul + li Click slower. VersionError is caused by clicking things off too fast (we're working on a fix). + li If you see an error, refresh before proceeding. + + p + small.muted By Lemoness, mariahm, crystalphoenix, aiseant, zoebeagle, cole, lefnire hr h5 02/01/2014 @@ -68,6 +75,7 @@ script(type='text/ng-template', id='modals/newStuff.html') p small.muted by @baconsaur & @DanielTheBard + hr h5 01/30/2014 table.table.table-striped tr diff --git a/views/shared/profiles/achievements.jade b/views/shared/profiles/achievements.jade index a7261a8c37..d18e0c739f 100644 --- a/views/shared/profiles/achievements.jade +++ b/views/shared/profiles/achievements.jade @@ -133,3 +133,9 @@ div(ng-if='profile.achievements.habitBirthday') =env.t('habitBirthdayText') hr +div(ng-if='profile.achievements.valentine') + .achievement.achievement-valentine + h5=env.t('adoringFriends') + small + =env.t('adoringFriendsText', {cards: "{{profile.achievements.valentine}}"}) + hr diff --git a/views/shared/tasks/lists.jade b/views/shared/tasks/lists.jade index be5f5d1d0f..dc9010d279 100644 --- a/views/shared/tasks/lists.jade +++ b/views/shared/tasks/lists.jade @@ -58,8 +58,9 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") span(bo-class='{"shop_{{item.key}} shop-sprite item-img": true}') p.task-text {{item.text}} - // Winter Event - ul.items.rewards(ng-if='main && list.type=="reward" && (user.items.special.snowball>0 || user.stats.buffs.snowball)') + // Events + ul.items.rewards(ng-if='main && list.type=="reward"') + li.task.reward-item(ng-if='user.items.special.snowball>0',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.snowball.notes}}') .task-meta-controls span.task-notes @@ -71,6 +72,7 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") span.shop_snowball(style='display:inline-block;vertical-align:top;') // main content p.task-text {{Content.spells.special.snowball.text}} + li.task.reward-item(ng-if='user.stats.buffs.snowball',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.salt.notes}}') .task-meta-controls span.task-notes @@ -84,6 +86,18 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") span.shop_salt.shop-sprite.item-img p.task-text {{Content.spells.special.salt.text}} + li.task.reward-item(popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.valentine.notes}}') + .task-meta-controls + span.task-notes + span.glyphicon.glyphicon-comment + //left-hand size commands + .task-controls + a.money.btn-buy.item-btn(ng-click='castStart(Content.spells.special.valentine)') + span.reward-cost {{Content.spells.special.valentine.value}} + span.shop_gold + span.inventory_special_valentine.shop-sprite.item-img + p.task-text {{Content.spells.special.valentine.text}} + // Spells ul.items(ng-if='main && list.type=="reward" && user.stats.class && !user.preferences.disableClasses') li.task.reward-item(ng-repeat='(k,spell) in Content.spells[user.stats.class]', ng-if='user.stats.lvl >= spell.lvl',popover-trigger='mouseenter', popover-placement='top', popover='{{spell.notes}}')