Merge branch 'develop' of https://github.com/HabitRPG/habitrpg into develop

Conflicts:
	views/shared/tasks/task.jade
This commit is contained in:
benmanley
2015-01-19 17:06:19 +00:00
36 changed files with 683 additions and 758 deletions

View File

@@ -16,10 +16,10 @@
"dependencies": {
"jquery": "~2.1.0",
"jquery.cookie": "~1.4.0",
"angular": "1.3.3",
"angular": "1.3.9",
"angular-ui": "~0.4.0",
"angular-sanitize": "1.3.3",
"angular-resource": "1.3.3",
"angular-sanitize": "1.3.9",
"angular-resource": "1.3.9",
"angular-ui-utils": "~0.1.0",
"angular-ui-select2": "git://github.com/angular-ui/ui-select2.git",
"angular-bootstrap": "~0.12.0",
@@ -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"
}
}

View File

@@ -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}
);
);

View File

@@ -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'])
// @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 = '<strong>Please reload</strong>, ' +
'"'+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');
}])

View File

@@ -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);

View File

@@ -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 <head>
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 <head>
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
});
}
}
}])

View File

@@ -211,7 +211,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;
@@ -275,7 +275,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) {
@@ -303,11 +303,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',

View File

@@ -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 = [];

View File

@@ -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()
}
});
}
]);

View File

@@ -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(){
@@ -221,7 +221,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) {

View File

@@ -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;
@@ -183,7 +184,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;

View File

@@ -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":

View File

@@ -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',

View File

@@ -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() {}
}
}
]);

View File

@@ -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
}
}]);

View File

@@ -4,63 +4,74 @@
* Services that persists and retrieves user from localStorage.
*/
angular.module('groupServices', ['ngResource']).
factory('Groups', ['ApiUrlService', '$resource', '$q', '$http', 'User',
function(ApiUrlService, $resource, $q, $http, User) {
var Group = $resource(ApiUrlService.get() + '/api/v2/groups/:gid',
{gid:'@_id', messageId: '@_messageId'},
{
//query: {method: "GET", isArray:false},
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

View File

@@ -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') + " <a href='http://www.kickstarter.com/profile/1823740484' target='_blank'>Justin</a>, " + 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') + " <a href='http://habitrpg.wikia.com' target='_blank'>" + window.env.t('habitWiki') + "</a> " + window.env.t('unlockFeaturesT2'),
placement: "right"
}
];
_.each(tourSteps, function(step){
if (env.worldDmg.guide) {
step.content = "<div><div class='npc_justin_broken float-left'></div>" + step.content + "</div>";
} else {
step.content = "<div><div class='npc_justin float-left'></div>" + step.content + "</div>";
}
});
$('.main-herobox').popover('destroy');
var tour = new Tour({
template: "<div class='popover'>" +
"<div class='arrow'></div><h3 class='popover-title'></h3><div class='popover-content'></div>" +
"<div class='popover-navigation'><div class='btn-group'>" +
"<button class='btn btn-sm btn-default' data-role='prev'>&laquo;</button>" +
"<button class='btn btn-sm btn-default' data-role='next'>&raquo;</button>" +
"<button class='btn btn-sm btn-default' data-role='pause-resume' data-pause-text='Pause' data-resume-text='Resume'>Pause</button></div>" +
"<button class='btn btn-sm btn-default' data-role='end'>" + window.env.t('endTour') + "</button></div></div>",
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 = "<button class='btn btn-sm btn-default' onClick=\"$('" + selector + "').popover('hide');return false;\">" + window.env.t('close') + "</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') + " <a href='http://www.kickstarter.com/profile/1823740484' target='_blank'>Justin</a>, " + 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') + " <a href='http://habitrpg.wikia.com' target='_blank'>" + window.env.t('habitWiki') + "</a> " + window.env.t('unlockFeaturesT2'),
placement: "right"
}
];
_.each(tourSteps, function(step){
if (env.worldDmg.guide) {
html = "<div><div class='npc_justin_broken float-left'></div>" + html + '<br/>' + button + '</div>';
step.content = "<div><div class='npc_justin_broken float-left'></div>" + step.content + "</div>";
} else {
html = "<div><div class='npc_justin float-left'></div>" + html + '<br/>' + button + '</div>';
step.content = "<div><div class='npc_justin float-left'></div>" + step.content + "</div>";
}
$(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: "<div class='popover'>" +
"<div class='arrow'></div><h3 class='popover-title'></h3><div class='popover-content'></div>" +
"<div class='popover-navigation'><div class='btn-group'>" +
"<button class='btn btn-sm btn-default' data-role='prev'>&laquo;</button>" +
"<button class='btn btn-sm btn-default' data-role='next'>&raquo;</button>" +
"<button class='btn btn-sm btn-default' data-role='pause-resume' data-pause-text='Pause' data-resume-text='Resume'>Pause</button></div>" +
"<button class='btn btn-sm btn-default' data-role='end'>" + window.env.t('endTour') + "</button></div></div>",
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 = "<button class='btn btn-sm btn-default' onClick=\"$('" + selector + "').popover('hide');return false;\">" + window.env.t('close') + "</button>";
if (env.worldDmg.guide) {
html = "<div><div class='npc_justin_broken float-left'></div>" + html + '<br/>' + button + '</div>';
} else {
html = "<div><div class='npc_justin float-left'></div>" + html + '<br/>' + button + '</div>';
}
$(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') + " <a target='_blank' href='http://habitrpg.wikia.com/wiki/Todos'>" + window.env.t('toDo') + "</a>."
}, {
orphan: true,
title: window.env.t('readMore'),
content: window.env.t('moreClass') + " <a href='http://habitrpg.wikia.com/wiki/Class_System' target='_blank'>Wikia</a>."
}
];
_.each(tourSteps, function(step){
if (env.worldDmg.guide) {
step.content = "<div><div class='npc_justin_broken float-left'></div>" + step.content + "</div>";
} else {
step.content = "<div><div class='npc_justin float-left'></div>" + step.content + "</div>";
}
});
$('.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') + " <a target='_blank' href='http://habitrpg.wikia.com/wiki/Todos'>" + window.env.t('toDo') + "</a>."
}, {
orphan: true,
title: window.env.t('readMore'),
content: window.env.t('moreClass') + " <a href='http://habitrpg.wikia.com/wiki/Class_System' target='_blank'>Wikia</a>."
}
];
_.each(tourSteps, function(step){
if (env.worldDmg.guide) {
step.content = "<div><div class='npc_justin_broken float-left'></div>" + step.content + "</div>";
} else {
step.content = "<div><div class='npc_justin float-left'></div>" + step.content + "</div>";
}
});
$('.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
};
}]);

View File

@@ -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;
}]);

View File

@@ -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 + " <span class='notification-icon shop_gold'></span> " + silver + " <span class='notification-icon shop_silver'></span>";
} else if (gold > 0) {
return "" + gold + " <span class='notification-icon shop_gold'></span>";
} else if (silver > 0) {
return "" + silver + " <span class='notification-icon shop_silver'></span>";
}
};
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 + " <span class='notification-icon shop_gold'></span> " + silver + " <span class='notification-icon shop_silver'></span>";
} else if (gold > 0) {
return "" + gold + " <span class='notification-icon shop_gold'></span>";
} else if (silver > 0) {
return "" + silver + " <span class='notification-icon shop_silver'></span>";
}
};
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');
}
};
}]);

View File

@@ -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 = {};

View File

@@ -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;
}]);

View File

@@ -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){

View File

@@ -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",

View File

@@ -660,10 +660,12 @@ questStart = function(req, res, next) {
})
group.quest.active = true;
if (quest.boss)
if (quest.boss) {
group.quest.progress.hp = quest.boss.hp;
else
if (quest.boss.rage) group.quest.progress.rage = 0;
} else {
group.quest.progress.collect = collected;
}
group.quest.members = questMembers;
group.markModified('quest'); // members & progress.collect are both Mixed types
parallel.push(function(cb2){group.save(cb2)});

View File

@@ -79,7 +79,7 @@ module.exports.errorHandler = function(err, req, res, next) {
error: "Uncaught error",
stack: (err.stack || err.message || err),
body: req.body, headers: req.header,
auth: (req.headers['x-api-user'] + ' | ' + req.headers['x-api-key']),
auth: req.headers['x-api-user'],
originalUrl: req.originalUrl
});
var message = err.message ? err.message : err;

View File

@@ -301,8 +301,17 @@ GroupSchema.statics.bossQuest = function(user, progress, cb) {
var down = progress.down * quest.boss.str; // multiply by boss strength
group.quest.progress.hp -= progress.up;
group.sendChat("`" + user.profile.name + " attacks " + quest.boss.name('en') + " for " + (progress.up.toFixed(1)) + " damage, " + quest.boss.name('en') + " attacks party for " + Math.abs(down).toFixed(1) + " damage.`");
group.sendChat("`" + user.profile.name + " attacks " + quest.boss.name('en') + " for " + (progress.up.toFixed(1)) + " damage, " + quest.boss.name('en') + " attacks party for " + Math.abs(down).toFixed(1) + " damage.`"); //TODO Create a party preferred language option so emits like this can be localized
// If boss has Rage, increment Rage as well
if (quest.boss.rage) {
group.quest.progress.rage += Math.abs(down);
if (group.quest.progress.rage >= quest.boss.rage.value) {
group.sendChat(quest.boss.rage.effect('en'));
group.quest.progress.rage = 0;
if (quest.boss.rage.healing) group.quest.progress.hp += (group.quest.progress.hp * quest.boss.rage.healing); //TODO To make Rage effects more expandable, let's turn these into functions in quest.boss.rage
}
}
// Everyone takes damage
var series = [
function(cb2){

View File

@@ -250,6 +250,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: {

View File

@@ -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:

View File

@@ -183,6 +183,11 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
p
| {{::Content.eggs.#{egg}.value}}&nbsp;
span.Pet_Currency_Gem1x.inline-gems
div(ng-show='(user.achievements.quests.trex + user.achievements.quests.trex_undead) > 1')
button.customize-option(popover='{{::Content.eggs.TRex.notes()}}', popover-title!=env.t("egg", {eggType: "{{Content.eggs.TRex.text()}}"}), popover-trigger='mouseenter', popover-placement='top', ng-click='purchase("eggs", Content.eggs.TRex)', class='Pet_Egg_TRex')
p
| {{::Content.eggs.TRex.value}}&nbsp;
span.Pet_Currency_Gem1x.inline-gems
li.customize-menu
menu.pets-menu(label=env.t('hatchingPotions'))

View File

@@ -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')
|&nbsp;
a(href='/#/options/settings/settings')=env.t('displayNameDescription2')
|&nbsp;
=env.t('displayNameDescription3')
span.muted(ng-hide='profile.profile.name') -&nbsp;
=env.t('none')
| &nbsp;-

View File

@@ -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')
|&nbsp;
a(href='/#/options/profile/profile')=env.t('loginNameDescription2')
|&nbsp;
=env.t('loginNameDescription3')
p=env.t('email')
|: {{::user.auth.local.email}}
p
small.muted
=env.t('emailChange1')
|&nbsp;
a(href='mailto:admin@habitrpg.com')=env.t('emailChange2')
|&nbsp;
=env.t('emailChange3')
hr
h5=env.t('changeUsername')
form(ng-submit='changeUsername(changeUser)', ng-show='user.auth.local')

View File

@@ -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})'
)
|&nbsp;
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

View File

@@ -1,4 +1,14 @@
h5 STRESS HAS STRUCK!
h5 TYRANNOSAUR PET QUEST, SPREAD THE WORD CHALLENGE, AND FIRST STRESS STRIKE!
tr
td
h5 Tyrannosaur Pet Quest
p In the <a href='https://habitrpg.com/#/options/inventory/drops' target='_blank'>Market</a> 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 <a href='https://habitrpg.com/#/options/groups/challenges/e1ad6d92-587d-4137-848e-4a1f643603ba' target='_blank'>here</a>!
tr
td
h5 World Boss: First Stress Strike!

View File

@@ -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

View File

@@ -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
@@ -63,9 +63,9 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
// Habits
.task-actions(ng-if='::task.type=="habit"')
// score() is overridden in challengesCtrl to do nothing
a(ng-if='task.up', ng-click='score(task,"up")')
a(ng-if='task.up', ng-click='applyingAction || score(task,"up")')
span.glyphicon.glyphicon-plus
a(ng-if='task.down', ng-click='score(task,"down")')
a(ng-if='task.down', ng-click='applyingAction || score(task,"down")')
span.glyphicon.glyphicon-minus
// Rewards

View File

@@ -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

View File

@@ -1,6 +1,6 @@
block vars
doctype html
html(ng-app='habitrpgStatic')
html(ng-app='habitrpg')
head
block title