Merge branch 'develop' into sabrecat/pet-201509
Conflicts: common/dist/sprites/habitrpg-shared.css common/dist/sprites/spritesmith2.css common/dist/sprites/spritesmith2.png common/dist/sprites/spritesmith3.css common/dist/sprites/spritesmith3.png common/dist/sprites/spritesmith4.css common/dist/sprites/spritesmith4.png
This commit is contained in:
@@ -110,8 +110,10 @@ html, body
|
||||
//move
|
||||
|
||||
.new-stuff-modal // a .modal-body
|
||||
h5
|
||||
h2, h3
|
||||
font-weight: 700
|
||||
font-size: 14px
|
||||
margin-top: 10px
|
||||
|
||||
.modal-fixed-height
|
||||
overflow-y: auto
|
||||
@@ -200,5 +202,5 @@ a.label
|
||||
*
|
||||
image-rendering: pixelated
|
||||
|
||||
.img-rendering-auto
|
||||
.img-rendering-auto, .emoji
|
||||
image-rendering: auto
|
||||
|
||||
@@ -45,6 +45,9 @@ a.hint:hover
|
||||
ul
|
||||
list-style-position: inside;
|
||||
|
||||
li.spaced
|
||||
margin: auto auto .5em 3em
|
||||
|
||||
.group-leave-join
|
||||
.glyphicon-ban-circle
|
||||
color: white;
|
||||
@@ -64,3 +67,6 @@ ul
|
||||
.flex-column
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.row-margin
|
||||
margin: auto auto 1em auto
|
||||
|
||||
@@ -25,16 +25,47 @@ body
|
||||
|
||||
.marketing
|
||||
text-align: center
|
||||
|
||||
|
||||
.static-old-news
|
||||
h2
|
||||
font-weight: 700
|
||||
font-size: 16px
|
||||
margin-top: 30px
|
||||
text-transform: uppercase
|
||||
h3
|
||||
font-weight: 700
|
||||
font-size: 14px
|
||||
margin-top: 20px
|
||||
.pull-right
|
||||
margin-left: 5px
|
||||
|
||||
#frontpage-play-button
|
||||
//box-shadow: 0 0 40px #494141;
|
||||
display:inline
|
||||
display: inline
|
||||
font-size: 20px
|
||||
min-width:100px
|
||||
height:50px
|
||||
min-width: 100px
|
||||
height: 50px
|
||||
|
||||
#about-page
|
||||
img
|
||||
box-shadow 0px 0px 10px 5px #888
|
||||
margin 10px
|
||||
max-width 500px
|
||||
|
||||
// Accordion tweaks for FAQ
|
||||
|
||||
a.h2.accordion
|
||||
display: block
|
||||
text-decoration: none
|
||||
|
||||
a.h2.accordion:before
|
||||
font-family: 'Glyphicons Halflings'
|
||||
color: grey
|
||||
content: "\e114"
|
||||
margin-right: 0.5em
|
||||
|
||||
a.h2.accordion.collapsed:before
|
||||
font-family: 'Glyphicons Halflings'
|
||||
color: grey
|
||||
content: "\e080"
|
||||
margin-right: 0.5em
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller('AutocompleteCtrl', ['$scope', '$timeout', 'Groups', 'User', 'InputCaret', function ($scope,$timeout,Groups,User,InputCaret) {
|
||||
$scope.clearUserlist = function() {
|
||||
$scope.response = [];
|
||||
$scope.usernames = [];
|
||||
}
|
||||
|
||||
$scope.filterUser = function(msg) {
|
||||
if (!$scope.query || !msg.user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore casing when checking for username
|
||||
var user = msg.user.toLowerCase();
|
||||
var text = $scope.query.text.toLowerCase();
|
||||
|
||||
return user.indexOf(text) == 0;
|
||||
}
|
||||
|
||||
$scope.performCompletion = function(msg) {
|
||||
$scope.autoComplete(msg);
|
||||
$scope.query = null;
|
||||
}
|
||||
|
||||
$scope.addNewUser = function(user) {
|
||||
if($.inArray(user.user,$scope.usernames) == -1) {
|
||||
user.username = user.user;
|
||||
$scope.usernames.push(user.user);
|
||||
$scope.response.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.clearUserlist();
|
||||
|
||||
$scope.chatChanged = function(newvalue,oldvalue){
|
||||
if($scope.group && $scope.group.chat && $scope.group.chat.length > 0){
|
||||
for(var i = 0; i < $scope.group.chat.length; i++) {
|
||||
$scope.addNewUser($scope.group.chat[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch('group.chat',$scope.chatChanged);
|
||||
|
||||
$scope.caretChanged = function(newCaretPos) {
|
||||
var relativeelement = $('.chat-form div:first');
|
||||
var textarea = $('.chat-form textarea');
|
||||
var userlist = $('.list-at-user');
|
||||
var offset = {
|
||||
x: textarea.offset().left - relativeelement.offset().left,
|
||||
y: textarea.offset().top - relativeelement.offset().top,
|
||||
};
|
||||
if(relativeelement) {
|
||||
var caretOffset = InputCaret.getPosition(textarea);
|
||||
userlist.css({
|
||||
left: caretOffset.left + offset.x,
|
||||
top: caretOffset.top + offset.y + 16
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.updateTimer = false;
|
||||
|
||||
$scope.$watch(function () { return $scope.caretPos; },function(newCaretPos) {
|
||||
if($scope.updateTimer){
|
||||
$timeout.cancel($scope.updateTimer)
|
||||
}
|
||||
$scope.updateTimer = $timeout(function(){
|
||||
$scope.caretChanged(newCaretPos);
|
||||
},$scope.watchDelay)
|
||||
});
|
||||
}]);
|
||||
@@ -0,0 +1,139 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller('ChatCtrl', ['$scope', 'Groups', 'Chat', 'User', '$http', 'ApiUrl', 'Notification', 'Members', '$rootScope', 'Analytics',
|
||||
function($scope, Groups, Chat, User, $http, ApiUrl, Notification, Members, $rootScope, Analytics){
|
||||
$scope.message = {content:''};
|
||||
$scope._sending = false;
|
||||
|
||||
$scope.isUserMentioned = function(user, message) {
|
||||
if(message.hasOwnProperty("highlight"))
|
||||
return message.highlight;
|
||||
message.highlight = false;
|
||||
var messagetext = message.text.toLowerCase();
|
||||
var username = user.profile.name;
|
||||
var mentioned = messagetext.indexOf(username.toLowerCase());
|
||||
var pattern = username+"([^\w]|$){1}";
|
||||
if(mentioned > -1) {
|
||||
var preceedingchar = messagetext.substring(mentioned-1,mentioned);
|
||||
if(mentioned == 0 || preceedingchar.trim() == '' || preceedingchar == '@'){
|
||||
var regex = new RegExp(pattern,'i');
|
||||
message.highlight = regex.test(messagetext);
|
||||
}
|
||||
}
|
||||
return message.highlight;
|
||||
}
|
||||
|
||||
$scope.postChat = function(group, message){
|
||||
if (_.isEmpty(message) || $scope._sending) return;
|
||||
$scope._sending = true;
|
||||
var previousMsg = (group.chat && group.chat[0]) ? group.chat[0].id : false;
|
||||
Chat.utils.postChat({gid: group._id, message:message, previousMsg: previousMsg}, undefined, function(data){
|
||||
if(data.chat){
|
||||
group.chat = data.chat;
|
||||
}else if(data.message){
|
||||
group.chat.unshift(data.message);
|
||||
}
|
||||
$scope.message.content = '';
|
||||
$scope._sending = false;
|
||||
if (group.type == 'party') {
|
||||
Analytics.updateUser({'partyID':group.id,'partySize':group.memberCount});
|
||||
}
|
||||
if (group.privacy == 'public'){
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy,'groupName':group.name,'message':message});
|
||||
} else {
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy});
|
||||
}
|
||||
}, function(err){
|
||||
$scope._sending = false;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.deleteChatMessage = function(group, message){
|
||||
if(message.uuid === User.user.id || (User.user.backer && User.user.contributor.admin)){
|
||||
var previousMsg = (group.chat && group.chat[0]) ? group.chat[0].id : false;
|
||||
if(confirm('Are you sure you want to delete this message?')){
|
||||
Chat.utils.deleteChatMessage({gid:group._id, messageId:message.id, previousMsg:previousMsg}, undefined, function(data){
|
||||
if(data.chat) group.chat = data.chat;
|
||||
|
||||
var i = _.findIndex(group.chat, {id: message.id});
|
||||
if(i !== -1) group.chat.splice(i, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.likeChatMessage = function(group,message) {
|
||||
if (message.uuid == User.user._id)
|
||||
return Notification.text(window.env.t('foreverAlone'));
|
||||
if (!message.likes) message.likes = {};
|
||||
if (message.likes[User.user._id]) {
|
||||
delete message.likes[User.user._id];
|
||||
} else {
|
||||
message.likes[User.user._id] = true;
|
||||
}
|
||||
Chat.utils.like({ gid:group._id, messageId:message.id }, undefined);
|
||||
}
|
||||
|
||||
$scope.flagChatMessage = function(groupId,message) {
|
||||
if(!message.flags) message.flags = {};
|
||||
if(message.flags[User.user._id])
|
||||
Notification.text(window.env.t('abuseAlreadyReported'));
|
||||
else {
|
||||
$scope.abuseObject = message;
|
||||
$scope.groupId = groupId;
|
||||
Members.selectMember(message.uuid, function(){
|
||||
$rootScope.openModal('abuse-flag',{
|
||||
controller:'MemberModalCtrl',
|
||||
scope: $scope
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.copyToDo = function(message) {
|
||||
var taskNotes = env.t("messageWroteIn", {
|
||||
user: message.uuid == 'system'
|
||||
? 'system'
|
||||
: '[' + message.user + '](' + env.BASE_URL + '/static/front/#?memberId=' + message.uuid + ')',
|
||||
group: '[' + $scope.group.name + '](' + window.location.href + ')'
|
||||
});
|
||||
|
||||
var newScope = $scope.$new();
|
||||
newScope.text = message.text;
|
||||
newScope.notes = taskNotes;
|
||||
|
||||
$rootScope.openModal('copyChatToDo',{
|
||||
controller:'CopyMessageModalCtrl',
|
||||
scope: newScope
|
||||
});
|
||||
};
|
||||
|
||||
$scope.sync = function(group){
|
||||
if(group.type == 'party') {
|
||||
group.$syncParty(); // Syncs the whole party, not just 15 members
|
||||
} else {
|
||||
group.$get();
|
||||
}
|
||||
// When the user clicks fetch recent messages we need to update
|
||||
// that the user has seen the new messages
|
||||
Chat.seenMessage(group._id);
|
||||
}
|
||||
|
||||
// List of Ordering options for the party members list
|
||||
$scope.partyOrderChoices = {
|
||||
'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')
|
||||
}
|
||||
|
||||
}]);
|
||||
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller("CopyMessageModalCtrl", ['$scope', 'User', 'Notification',
|
||||
function($scope, User, Notification){
|
||||
$scope.saveTodo = function() {
|
||||
var newTask = {
|
||||
text: $scope.text,
|
||||
type: 'todo',
|
||||
notes: $scope.notes
|
||||
};
|
||||
|
||||
User.user.ops.addTask({body:newTask});
|
||||
Notification.text(window.env.t('messageAddedAsToDo'));
|
||||
|
||||
$scope.$close();
|
||||
}
|
||||
}]);
|
||||
@@ -7,13 +7,13 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||
if (!group.quest || !group.quest.members) return false;
|
||||
if (group.quest.active) return false; // quest is started, not pending
|
||||
return userid in group.quest.members && group.quest.members[userid] != false;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.isMemberOfRunningQuest = function(userid, group) {
|
||||
if (!group.quest || !group.quest.members) return false;
|
||||
if (!group.quest.active) return false; // quest is pending, not started
|
||||
return group.quest.members[userid];
|
||||
}
|
||||
};
|
||||
|
||||
$scope.isMemberOfGroup = function(userid, group){
|
||||
|
||||
@@ -32,11 +32,11 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||
if (!group.members) return false;
|
||||
var memberIds = _.map(group.members, function(x){return x._id});
|
||||
return ~(memberIds.indexOf(userid));
|
||||
}
|
||||
};
|
||||
|
||||
$scope.isMember = function(user, group){
|
||||
return ~(group.members.indexOf(user._id));
|
||||
}
|
||||
};
|
||||
|
||||
$scope.Members = Members;
|
||||
$scope._editing = {group:false};
|
||||
@@ -45,13 +45,13 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||
if(group._newLeader && group._newLeader._id) group.leader = group._newLeader._id;
|
||||
group.$save();
|
||||
group._editing = false;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.deleteAllMessages = function() {
|
||||
if (confirm(window.env.t('confirmDeleteAllMessages'))) {
|
||||
User.user.ops.clearPMs({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ------ Modals ------
|
||||
|
||||
@@ -69,7 +69,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||
$rootScope.openModal('member', {controller:'MemberModalCtrl', windowClass:'profile-modal', size:'lg'});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$scope.removeMember = function(group, member, isMember){
|
||||
@@ -80,7 +80,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||
isMember: isMember
|
||||
};
|
||||
$rootScope.openModal('remove-member', {scope: $scope});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.confirmRemoveMember = function(confirm){
|
||||
if(confirm){
|
||||
@@ -100,31 +100,21 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||
}else{
|
||||
$scope.removeMemberData = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.openInviteModal = function(group){
|
||||
$rootScope.openModal('invite-friends', {controller:'InviteToGroupCtrl', resolve:
|
||||
{injectedGroup: function(){
|
||||
return group;
|
||||
}}});
|
||||
};
|
||||
|
||||
//var serializeQs = function(obj, prefix){
|
||||
// var str = [];
|
||||
// for(var p in obj) {
|
||||
// if (obj.hasOwnProperty(p)) {
|
||||
// var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p];
|
||||
// str.push(typeof v == "object" ?
|
||||
// serializeQs(v, k) :
|
||||
// encodeURIComponent(k) + "=" + encodeURIComponent(v));
|
||||
// }
|
||||
// }
|
||||
// return str.join("&");
|
||||
//}
|
||||
//
|
||||
//$scope.inviteLink = function(obj){
|
||||
// return window.env.BASE_URL + '?' + serializeQs({partyInvite: obj});
|
||||
//}
|
||||
$scope.openInviteModal = function(group){
|
||||
if (group.type !== 'party' && group.type !== 'guild') {
|
||||
return console.log('Invalid group type.')
|
||||
}
|
||||
$rootScope.openModal('invite-' + group.type, {
|
||||
controller:'InviteToGroupCtrl',
|
||||
resolve: {
|
||||
injectedGroup: function(){
|
||||
return group;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.quickReply = function(uid) {
|
||||
Members.selectMember(uid, function(){
|
||||
@@ -132,457 +122,4 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||
});
|
||||
}
|
||||
|
||||
}])
|
||||
|
||||
.controller('InviteToGroupCtrl', ['$scope', 'User', 'Groups', 'injectedGroup', '$http', 'Notification', function($scope, User, Groups, injectedGroup, $http, Notification){
|
||||
$scope.group = injectedGroup;
|
||||
|
||||
$scope.inviter = User.user.profile.name;
|
||||
$scope.emails = [{name:"",email:""},{name:"",email:""}];
|
||||
|
||||
$scope.inviteEmails = function(){
|
||||
Groups.Group.invite({gid: $scope.group._id}, {inviter: $scope.inviter, emails: $scope.emails}, function(){
|
||||
Notification.text("Invitation(s) sent!");
|
||||
$scope.emails = [{name:'',email:''},{name:'',email:''}];
|
||||
}, function(){
|
||||
$scope.emails = [{name:'',email:''},{name:'',email:''}];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.invite = function(){
|
||||
Groups.Group.invite({gid: $scope.group._id}, {uuids: [$scope.invitee]}, function(){
|
||||
Notification.text("Invitation(s) sent!");
|
||||
$scope.invitee = '';
|
||||
}, function(){
|
||||
$scope.invitee = '';
|
||||
});
|
||||
};
|
||||
}])
|
||||
|
||||
.controller('AutocompleteCtrl', ['$scope', '$timeout', 'Groups', 'User', 'InputCaret', function ($scope,$timeout,Groups,User,InputCaret) {
|
||||
$scope.clearUserlist = function() {
|
||||
$scope.response = [];
|
||||
$scope.usernames = [];
|
||||
}
|
||||
|
||||
$scope.filterUser = function(msg) {
|
||||
if (!$scope.query || !msg.user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore casing when checking for username
|
||||
var user = msg.user.toLowerCase();
|
||||
var text = $scope.query.text.toLowerCase();
|
||||
|
||||
return user.indexOf(text) == 0;
|
||||
}
|
||||
|
||||
$scope.performCompletion = function(msg) {
|
||||
$scope.autoComplete(msg);
|
||||
$scope.query = null;
|
||||
}
|
||||
|
||||
$scope.addNewUser = function(user) {
|
||||
if($.inArray(user.user,$scope.usernames) == -1) {
|
||||
user.username = user.user;
|
||||
$scope.usernames.push(user.user);
|
||||
$scope.response.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.clearUserlist();
|
||||
|
||||
$scope.chatChanged = function(newvalue,oldvalue){
|
||||
if($scope.group && $scope.group.chat && $scope.group.chat.length > 0){
|
||||
for(var i = 0; i < $scope.group.chat.length; i++) {
|
||||
$scope.addNewUser($scope.group.chat[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch('group.chat',$scope.chatChanged);
|
||||
|
||||
$scope.caretChanged = function(newCaretPos) {
|
||||
var relativeelement = $('.chat-form div:first');
|
||||
var textarea = $('.chat-form textarea');
|
||||
var userlist = $('.list-at-user');
|
||||
var offset = {
|
||||
x: textarea.offset().left - relativeelement.offset().left,
|
||||
y: textarea.offset().top - relativeelement.offset().top,
|
||||
};
|
||||
if(relativeelement) {
|
||||
var caretOffset = InputCaret.getPosition(textarea);
|
||||
userlist.css({
|
||||
left: caretOffset.left + offset.x,
|
||||
top: caretOffset.top + offset.y + 16
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.updateTimer = false;
|
||||
|
||||
$scope.$watch(function () { return $scope.caretPos; },function(newCaretPos) {
|
||||
if($scope.updateTimer){
|
||||
$timeout.cancel($scope.updateTimer)
|
||||
}
|
||||
$scope.updateTimer = $timeout(function(){
|
||||
$scope.caretChanged(newCaretPos);
|
||||
},$scope.watchDelay)
|
||||
});
|
||||
}])
|
||||
|
||||
.controller('ChatCtrl', ['$scope', 'Groups', 'Chat', 'User', '$http', 'ApiUrl', 'Notification', 'Members', '$rootScope', 'Analytics',
|
||||
function($scope, Groups, Chat, User, $http, ApiUrl, Notification, Members, $rootScope, Analytics){
|
||||
$scope.message = {content:''};
|
||||
$scope._sending = false;
|
||||
|
||||
$scope.isUserMentioned = function(user, message) {
|
||||
if(message.hasOwnProperty("highlight"))
|
||||
return message.highlight;
|
||||
message.highlight = false;
|
||||
var messagetext = message.text.toLowerCase();
|
||||
var username = user.profile.name;
|
||||
var mentioned = messagetext.indexOf(username.toLowerCase());
|
||||
var pattern = username+"([^\w]|$){1}";
|
||||
if(mentioned > -1) {
|
||||
var preceedingchar = messagetext.substring(mentioned-1,mentioned);
|
||||
if(mentioned == 0 || preceedingchar.trim() == '' || preceedingchar == '@'){
|
||||
var regex = new RegExp(pattern,'i');
|
||||
message.highlight = regex.test(messagetext);
|
||||
}
|
||||
}
|
||||
return message.highlight;
|
||||
}
|
||||
|
||||
$scope.postChat = function(group, message){
|
||||
if (_.isEmpty(message) || $scope._sending) return;
|
||||
$scope._sending = true;
|
||||
var previousMsg = (group.chat && group.chat[0]) ? group.chat[0].id : false;
|
||||
Chat.utils.postChat({gid: group._id, message:message, previousMsg: previousMsg}, undefined, function(data){
|
||||
if(data.chat){
|
||||
group.chat = data.chat;
|
||||
}else if(data.message){
|
||||
group.chat.unshift(data.message);
|
||||
}
|
||||
$scope.message.content = '';
|
||||
$scope._sending = false;
|
||||
if (group.type == 'party') {
|
||||
Analytics.updateUser({'partyID':group.id,'partySize':group.memberCount});
|
||||
}
|
||||
if (group.privacy == 'public'){
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy,'groupName':group.name,'message':message});
|
||||
} else {
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy});
|
||||
}
|
||||
}, function(err){
|
||||
$scope._sending = false;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.deleteChatMessage = function(group, message){
|
||||
if(message.uuid === User.user.id || (User.user.backer && User.user.contributor.admin)){
|
||||
var previousMsg = (group.chat && group.chat[0]) ? group.chat[0].id : false;
|
||||
if(confirm('Are you sure you want to delete this message?')){
|
||||
Chat.utils.deleteChatMessage({gid:group._id, messageId:message.id, previousMsg:previousMsg}, undefined, function(data){
|
||||
if(data.chat) group.chat = data.chat;
|
||||
|
||||
var i = _.findIndex(group.chat, {id: message.id});
|
||||
if(i !== -1) group.chat.splice(i, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.likeChatMessage = function(group,message) {
|
||||
if (message.uuid == User.user._id)
|
||||
return Notification.text(window.env.t('foreverAlone'));
|
||||
if (!message.likes) message.likes = {};
|
||||
if (message.likes[User.user._id]) {
|
||||
delete message.likes[User.user._id];
|
||||
} else {
|
||||
message.likes[User.user._id] = true;
|
||||
}
|
||||
Chat.utils.like({ gid:group._id, messageId:message.id }, undefined);
|
||||
}
|
||||
|
||||
$scope.flagChatMessage = function(groupId,message) {
|
||||
if(!message.flags) message.flags = {};
|
||||
if(message.flags[User.user._id])
|
||||
Notification.text(window.env.t('abuseAlreadyReported'));
|
||||
else {
|
||||
$scope.abuseObject = message;
|
||||
$scope.groupId = groupId;
|
||||
Members.selectMember(message.uuid, function(){
|
||||
$rootScope.openModal('abuse-flag',{
|
||||
controller:'MemberModalCtrl',
|
||||
scope: $scope
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.copyToDo = function(message) {
|
||||
var taskNotes = env.t("messageWroteIn", {
|
||||
user: message.uuid == 'system'
|
||||
? 'system'
|
||||
: '[' + message.user + '](' + env.BASE_URL + '/static/front/#?memberId=' + message.uuid + ')',
|
||||
group: '[' + $scope.group.name + '](' + window.location.href + ')'
|
||||
});
|
||||
|
||||
var newScope = $scope.$new();
|
||||
newScope.text = message.text;
|
||||
newScope.notes = taskNotes;
|
||||
|
||||
$rootScope.openModal('copyChatToDo',{
|
||||
controller:'CopyMessageModalCtrl',
|
||||
scope: newScope
|
||||
});
|
||||
};
|
||||
|
||||
$scope.sync = function(group){
|
||||
if(group.type == 'party') {
|
||||
group.$syncParty(); // Syncs the whole party, not just 15 members
|
||||
} else {
|
||||
group.$get();
|
||||
}
|
||||
// When the user clicks fetch recent messages we need to update
|
||||
// that the user has seen the new messages
|
||||
Chat.seenMessage(group._id);
|
||||
}
|
||||
|
||||
// List of Ordering options for the party members list
|
||||
$scope.partyOrderChoices = {
|
||||
'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', 'Analytics',
|
||||
function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile, Analytics) {
|
||||
$scope.groups = {
|
||||
guilds: Groups.myGuilds(),
|
||||
"public": Groups.publicGuilds()
|
||||
}
|
||||
|
||||
$scope.type = 'guild';
|
||||
$scope.text = window.env.t('guild');
|
||||
var newGroup = function(){
|
||||
return new Groups.Group({type:'guild', privacy:'private'});
|
||||
}
|
||||
$scope.newGroup = newGroup()
|
||||
$scope.create = function(group){
|
||||
if (User.user.balance < 1)
|
||||
return $rootScope.openModal('buyGems', {track:"Gems > Create Group"});
|
||||
|
||||
if (confirm(window.env.t('confirmGuild'))) {
|
||||
group.$save(function(saved){
|
||||
if (saved.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy,'groupName':saved.name})}
|
||||
else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy})}
|
||||
$rootScope.hardRedirect('/#/options/groups/guilds/' + saved._id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.join = function(group){
|
||||
// If we're accepting an invitation, we don't have the actual group object, but a faux group object (for performance
|
||||
// purposes) {id, name}. Let's trick ngResource into thinking we have a group, so we can call the same $join
|
||||
// function (server calls .attachGroup(), which finds group by _id and handles this properly)
|
||||
if (group.id && !group._id) {
|
||||
group = new Groups.Group({_id:group.id});
|
||||
}
|
||||
|
||||
group.$join(function(joined){
|
||||
if (joined.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy,'groupName':joined.name})}
|
||||
else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy})}
|
||||
$rootScope.hardRedirect('/#/options/groups/guilds/' + joined._id);
|
||||
})
|
||||
}
|
||||
|
||||
$scope.leave = function(keep) {
|
||||
if (keep == 'cancel') {
|
||||
$scope.selectedGroup = undefined;
|
||||
$scope.popoverEl.popover('destroy');
|
||||
} else {
|
||||
Groups.Group.leave({gid: $scope.selectedGroup._id, keep:keep}, undefined, function(){
|
||||
$rootScope.hardRedirect('/#/options/groups/guilds');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.clickLeave = function(group, $event){
|
||||
$scope.selectedGroup = group;
|
||||
$scope.popoverEl = $($event.target);
|
||||
var html, title;
|
||||
Challenges.Challenge.query(function(challenges) {
|
||||
challenges = _.pluck(_.filter(challenges, function(c) {
|
||||
return c.group._id == group._id;
|
||||
}), '_id');
|
||||
if (_.intersection(challenges, User.user.challenges).length > 0) {
|
||||
html = $compile(
|
||||
'<a ng-controller="GroupsCtrl" ng-click="leave(\'remove-all\')">' + window.env.t('removeTasks') + '</a><br/>\n<a ng-click="leave(\'keep-all\')">' + window.env.t('keepTasks') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
title = window.env.t('leaveGroupCha');
|
||||
} else {
|
||||
html = $compile(
|
||||
'<a ng-controller="GroupsCtrl" ng-click="leave(\'keep-all\')">' + window.env.t('confirm') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
title = window.env.t('leaveGroup')
|
||||
}
|
||||
$scope.popoverEl.popover('destroy').popover({
|
||||
html: true,
|
||||
placement: 'top',
|
||||
trigger: 'manual',
|
||||
title: title,
|
||||
content: html
|
||||
}).popover('show');
|
||||
});
|
||||
}
|
||||
|
||||
$scope.reject = function(guild){
|
||||
var i = _.findIndex(User.user.invitations.guilds, {id:guild.id});
|
||||
if (~i){
|
||||
User.user.invitations.guilds.splice(i, 1);
|
||||
User.set({'invitations.guilds':User.user.invitations.guilds});
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','Challenges','$state','$compile','Analytics','Quests',
|
||||
function($rootScope,$scope,Groups,Chat,User,Challenges,$state,$compile,Analytics,Quests) {
|
||||
$scope.type = 'party';
|
||||
$scope.text = window.env.t('party');
|
||||
$scope.group = $rootScope.party = Groups.party();
|
||||
$scope.newGroup = new Groups.Group({type:'party'});
|
||||
$scope.inviteOrStartParty = Groups.inviteOrStartParty;
|
||||
$scope.questInit = Quests.questInit;
|
||||
|
||||
if ($state.is('options.social.party')) {
|
||||
$scope.group.$syncParty(); // Sync party automatically when navigating to party page
|
||||
}
|
||||
|
||||
Chat.seenMessage($scope.group._id);
|
||||
|
||||
$scope.create = function(group){
|
||||
group.$save(function(){
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'party','privacy':'private'});
|
||||
Analytics.updateUser({'partyID':group.id,'partySize':1});
|
||||
$rootScope.hardRedirect('/#/options/groups/party');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.join = function(party){
|
||||
var group = new Groups.Group({_id: party.id, name: party.name});
|
||||
group.$join(function(){
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'party','privacy':'private'});
|
||||
Analytics.updateUser({'partyID':party.id});
|
||||
$rootScope.hardRedirect('/#/options/groups/party');
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: refactor guild and party leave into one function
|
||||
$scope.leave = function(keep) {
|
||||
if (keep == 'cancel') {
|
||||
$scope.selectedGroup = undefined;
|
||||
$scope.popoverEl.popover('destroy');
|
||||
} else {
|
||||
Groups.Group.leave({gid: $scope.selectedGroup._id, keep:keep}, undefined, function(){
|
||||
Analytics.updateUser({'partySize':null,'partyID':null});
|
||||
$rootScope.hardRedirect('/#/options/groups/party');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: refactor guild and party clickLeave into one function
|
||||
$scope.clickLeave = function(group, $event){
|
||||
$scope.selectedGroup = group;
|
||||
$scope.popoverEl = $($event.target);
|
||||
var html, title;
|
||||
Challenges.Challenge.query(function(challenges) {
|
||||
challenges = _.pluck(_.filter(challenges, function(c) {
|
||||
return c.group._id == group._id;
|
||||
}), '_id');
|
||||
if (_.intersection(challenges, User.user.challenges).length > 0) {
|
||||
html = $compile(
|
||||
'<a ng-controller="GroupsCtrl" ng-click="leave(\'remove-all\')">' + window.env.t('removeTasks') + '</a><br/>\n<a ng-click="leave(\'keep-all\')">' + window.env.t('keepTasks') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
title = window.env.t('leavePartyCha');
|
||||
} else {
|
||||
html = $compile(
|
||||
'<a ng-controller="GroupsCtrl" ng-click="leave(\'keep-all\')">' + window.env.t('confirm') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
title = window.env.t('leaveParty');
|
||||
}
|
||||
$scope.popoverEl.popover('destroy').popover({
|
||||
html: true,
|
||||
placement: 'top',
|
||||
trigger: 'manual',
|
||||
title: title,
|
||||
content: html
|
||||
}).popover('show');
|
||||
});
|
||||
}
|
||||
|
||||
$scope.reject = function(){
|
||||
//User.user.invitations.party = undefined;
|
||||
User.set({'invitations.party':{}});
|
||||
}
|
||||
|
||||
$scope.questCancel = function(party){
|
||||
if (!confirm(window.env.t('sureCancel'))) return;
|
||||
Groups.questCancel(party);
|
||||
}
|
||||
|
||||
$scope.questAbort = function(party){
|
||||
if (!confirm(window.env.t('sureAbort'))) return;
|
||||
if (!confirm(window.env.t('doubleSureAbort'))) return;
|
||||
Groups.questAbort(party);
|
||||
}
|
||||
|
||||
$scope.questAccept = function(party){
|
||||
Groups.questAccept(party);
|
||||
};
|
||||
|
||||
$scope.questReject = function(party){
|
||||
Groups.questReject(party);
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
.controller("TavernCtrl", ['$scope', 'Groups', 'User',
|
||||
function($scope, Groups, User) {
|
||||
$scope.group = Groups.tavern();
|
||||
$scope.toggleUserTier = function($event) {
|
||||
$($event.target).next().toggle();
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
.controller("CopyMessageModalCtrl", ['$scope', 'User', 'Notification',
|
||||
function($scope, User, Notification){
|
||||
$scope.saveTodo = function() {
|
||||
var newTask = {
|
||||
text: $scope.text,
|
||||
type: 'todo',
|
||||
notes: $scope.notes
|
||||
};
|
||||
|
||||
User.user.ops.addTask({body:newTask});
|
||||
Notification.text(window.env.t('messageAddedAsToDo'));
|
||||
|
||||
$scope.$close();
|
||||
}
|
||||
}
|
||||
]);
|
||||
}]);
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', 'Analytics',
|
||||
function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile, Analytics) {
|
||||
$scope.groups = {
|
||||
guilds: Groups.myGuilds(),
|
||||
"public": Groups.publicGuilds()
|
||||
}
|
||||
|
||||
$scope.type = 'guild';
|
||||
$scope.text = window.env.t('guild');
|
||||
var newGroup = function(){
|
||||
return new Groups.Group({type:'guild', privacy:'private'});
|
||||
}
|
||||
$scope.newGroup = newGroup()
|
||||
$scope.create = function(group){
|
||||
if (User.user.balance < 1)
|
||||
return $rootScope.openModal('buyGems', {track:"Gems > Create Group"});
|
||||
|
||||
if (confirm(window.env.t('confirmGuild'))) {
|
||||
group.$save(function(saved){
|
||||
if (saved.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy,'groupName':saved.name})}
|
||||
else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy})}
|
||||
$rootScope.hardRedirect('/#/options/groups/guilds/' + saved._id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.join = function(group){
|
||||
// If we're accepting an invitation, we don't have the actual group object, but a faux group object (for performance
|
||||
// purposes) {id, name}. Let's trick ngResource into thinking we have a group, so we can call the same $join
|
||||
// function (server calls .attachGroup(), which finds group by _id and handles this properly)
|
||||
if (group.id && !group._id) {
|
||||
group = new Groups.Group({_id:group.id});
|
||||
}
|
||||
|
||||
group.$join(function(joined){
|
||||
if (joined.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy,'groupName':joined.name})}
|
||||
else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy})}
|
||||
$rootScope.hardRedirect('/#/options/groups/guilds/' + joined._id);
|
||||
})
|
||||
}
|
||||
|
||||
$scope.leave = function(keep) {
|
||||
if (keep == 'cancel') {
|
||||
$scope.selectedGroup = undefined;
|
||||
$scope.popoverEl.popover('destroy');
|
||||
} else {
|
||||
Groups.Group.leave({gid: $scope.selectedGroup._id, keep:keep}, undefined, function(){
|
||||
$rootScope.hardRedirect('/#/options/groups/guilds');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.clickLeave = function(group, $event){
|
||||
$scope.selectedGroup = group;
|
||||
$scope.popoverEl = $($event.target);
|
||||
var html, title;
|
||||
Challenges.Challenge.query(function(challenges) {
|
||||
challenges = _.pluck(_.filter(challenges, function(c) {
|
||||
return c.group._id == group._id;
|
||||
}), '_id');
|
||||
if (_.intersection(challenges, User.user.challenges).length > 0) {
|
||||
html = $compile(
|
||||
'<a ng-controller="GroupsCtrl" ng-click="leave(\'remove-all\')">' + window.env.t('removeTasks') + '</a><br/>\n<a ng-click="leave(\'keep-all\')">' + window.env.t('keepTasks') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
title = window.env.t('leaveGroupCha');
|
||||
} else {
|
||||
html = $compile(
|
||||
'<a ng-controller="GroupsCtrl" ng-click="leave(\'keep-all\')">' + window.env.t('confirm') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
title = window.env.t('leaveGroup')
|
||||
}
|
||||
$scope.popoverEl.popover('destroy').popover({
|
||||
html: true,
|
||||
placement: 'top',
|
||||
trigger: 'manual',
|
||||
title: title,
|
||||
content: html
|
||||
}).popover('show');
|
||||
});
|
||||
}
|
||||
|
||||
$scope.reject = function(guild){
|
||||
var i = _.findIndex(User.user.invitations.guilds, {id:guild.id});
|
||||
if (~i){
|
||||
User.user.invitations.guilds.splice(i, 1);
|
||||
User.set({'invitations.guilds':User.user.invitations.guilds});
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller('InviteToGroupCtrl', ['$scope', 'User', 'Groups', 'injectedGroup', '$http', 'Notification', function($scope, User, Groups, injectedGroup, $http, Notification) {
|
||||
$scope.group = injectedGroup;
|
||||
|
||||
$scope.inviter = User.user.profile.name;
|
||||
_resetInvitees();
|
||||
|
||||
$scope.addUuid = function() {
|
||||
$scope.invitees.push({uuid: ''});
|
||||
};
|
||||
|
||||
$scope.addEmail = function() {
|
||||
$scope.emails.push({name: '', email: ''});
|
||||
};
|
||||
|
||||
$scope.inviteNewUsers = function(inviteMethod) {
|
||||
if (!$scope.group._id) {
|
||||
$scope.group.name = $scope.group.name || env.t('possessiveParty', {name: User.user.profile.name});
|
||||
return $scope.group.$save()
|
||||
.then(function(res) {
|
||||
_inviteByMethod(inviteMethod);
|
||||
});
|
||||
}
|
||||
|
||||
_inviteByMethod(inviteMethod);
|
||||
};
|
||||
|
||||
function _inviteByMethod(inviteMethod) {
|
||||
var invitationDetails;
|
||||
|
||||
if (inviteMethod === 'email') {
|
||||
var emails = _getEmails();
|
||||
invitationDetails = {inviter: $scope.inviter, emails: emails};
|
||||
} else if (inviteMethod === 'uuid') {
|
||||
var uuids = _getOnlyUuids();
|
||||
invitationDetails = {uuids: uuids};
|
||||
} else {
|
||||
return console.log('Invalid invite method.')
|
||||
}
|
||||
|
||||
Groups.Group.invite({gid: $scope.group._id}, invitationDetails, function(){
|
||||
Notification.text(window.env.t('invitationsSent'));
|
||||
_resetInvitees();
|
||||
}, function(){
|
||||
_resetInvitees();
|
||||
});
|
||||
}
|
||||
|
||||
function _getOnlyUuids() {
|
||||
var uuids = _.pluck($scope.invitees, 'uuid');
|
||||
var filteredUuids = _.filter(uuids, function(id) {
|
||||
return id != '';
|
||||
});
|
||||
return filteredUuids;
|
||||
}
|
||||
|
||||
function _getEmails() {
|
||||
var emails = _.filter($scope.emails, function(obj) {
|
||||
return obj.email != '';
|
||||
});
|
||||
return emails;
|
||||
}
|
||||
|
||||
function _resetInvitees() {
|
||||
var emptyEmails = [{name:"",email:""},{name:"",email:""}];
|
||||
var emptyInvitees = [{uuid: ''}];
|
||||
|
||||
$scope.emails = emptyEmails;
|
||||
$scope.invitees = emptyInvitees;
|
||||
}
|
||||
}]);
|
||||
@@ -8,6 +8,8 @@ habitrpg.controller('NotificationCtrl',
|
||||
if (after <= 0){
|
||||
$rootScope.playSound('Death');
|
||||
$rootScope.openModal('death', {keyboard:false, backdrop:'static'});
|
||||
} else if (after <= 30 && !User.user.flags.warnedLowHealth) {
|
||||
$rootScope.openModal('lowHealth', {keyboard:false, backdrop:'static', controller:'UserCtrl', track:'Health Warning'});
|
||||
}
|
||||
if (after == before) return;
|
||||
if (User.user.stats.lvl == 0) return;
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','Challenges','$state','$compile','Analytics','Quests',
|
||||
function($rootScope,$scope,Groups,Chat,User,Challenges,$state,$compile,Analytics,Quests) {
|
||||
$scope.type = 'party';
|
||||
$scope.text = window.env.t('party');
|
||||
$scope.group = $rootScope.party = Groups.party();
|
||||
$scope.newGroup = new Groups.Group({type:'party'});
|
||||
$scope.inviteOrStartParty = Groups.inviteOrStartParty;
|
||||
$scope.questInit = Quests.questInit;
|
||||
|
||||
if ($state.is('options.social.party')) {
|
||||
$scope.group.$syncParty(); // Sync party automatically when navigating to party page
|
||||
}
|
||||
|
||||
Chat.seenMessage($scope.group._id);
|
||||
|
||||
$scope.create = function(group){
|
||||
if (!group.name) group.name = env.t('possessiveParty', {name: User.user.profile.name});
|
||||
group.$save(function(){
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'party','privacy':'private'});
|
||||
Analytics.updateUser({'partyID':group.id,'partySize':1});
|
||||
$rootScope.hardRedirect('/#/options/groups/party');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.join = function(party){
|
||||
var group = new Groups.Group({_id: party.id, name: party.name});
|
||||
group.$join(function(){
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'party','privacy':'private'});
|
||||
Analytics.updateUser({'partyID':party.id});
|
||||
$rootScope.hardRedirect('/#/options/groups/party');
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: refactor guild and party leave into one function
|
||||
$scope.leave = function(keep) {
|
||||
if (keep == 'cancel') {
|
||||
$scope.selectedGroup = undefined;
|
||||
$scope.popoverEl.popover('destroy');
|
||||
} else {
|
||||
Groups.Group.leave({gid: $scope.selectedGroup._id, keep:keep}, undefined, function(){
|
||||
Analytics.updateUser({'partySize':null,'partyID':null});
|
||||
$rootScope.hardRedirect('/#/options/groups/party');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: refactor guild and party clickLeave into one function
|
||||
$scope.clickLeave = function(group, $event){
|
||||
Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Leave Party'});
|
||||
$scope.selectedGroup = group;
|
||||
$scope.popoverEl = $($event.target);
|
||||
var html, title;
|
||||
Challenges.Challenge.query(function(challenges) {
|
||||
challenges = _.pluck(_.filter(challenges, function(c) {
|
||||
return c.group._id == group._id;
|
||||
}), '_id');
|
||||
if (_.intersection(challenges, User.user.challenges).length > 0) {
|
||||
html = $compile(
|
||||
'<a ng-controller="GroupsCtrl" ng-click="leave(\'remove-all\')">' + window.env.t('removeTasks') + '</a><br/>\n<a ng-click="leave(\'keep-all\')">' + window.env.t('keepTasks') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
title = window.env.t('leavePartyCha');
|
||||
} else {
|
||||
html = $compile(
|
||||
'<a ng-controller="GroupsCtrl" ng-click="leave(\'keep-all\')">' + window.env.t('confirm') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
|
||||
)($scope);
|
||||
title = window.env.t('leaveParty');
|
||||
}
|
||||
$scope.popoverEl.popover('destroy').popover({
|
||||
html: true,
|
||||
placement: 'top',
|
||||
trigger: 'manual',
|
||||
title: title,
|
||||
content: html
|
||||
}).popover('show');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.clickStartQuest = function(){
|
||||
Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Start a Quest'});
|
||||
var hasQuests = _.find(User.user.items.quests, function(quest) {
|
||||
return quest > 0;
|
||||
});
|
||||
|
||||
if (hasQuests){
|
||||
$rootScope.openModal("ownedQuests", { controller:"InventoryCtrl" });
|
||||
} else {
|
||||
$rootScope.$state.go('options.inventory.quests');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.leaveOldPartyAndJoinNewParty = function(newPartyId, newPartyName) {
|
||||
if (confirm('Are you sure you want to delete your party and join ' + newPartyName + '?')) {
|
||||
Groups.Group.leave({gid: Groups.party()._id, keep:false}, undefined, function() {
|
||||
$scope.group = {
|
||||
loadingNewParty: true
|
||||
};
|
||||
$scope.join({ id: newPartyId, name: newPartyName });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.reject = function(){
|
||||
User.set({'invitations.party':{}});
|
||||
}
|
||||
|
||||
$scope.questCancel = function(party){
|
||||
if (!confirm(window.env.t('sureCancel'))) return;
|
||||
Groups.questCancel(party);
|
||||
}
|
||||
|
||||
$scope.questAbort = function(party){
|
||||
if (!confirm(window.env.t('sureAbort'))) return;
|
||||
if (!confirm(window.env.t('doubleSureAbort'))) return;
|
||||
Groups.questAbort(party);
|
||||
}
|
||||
|
||||
$scope.questLeave = function(party){
|
||||
if (!confirm(window.env.t('sureLeave'))) return;
|
||||
|
||||
delete $scope.group.quest.members[User.user._id];
|
||||
Groups.questLeave(party);
|
||||
}
|
||||
|
||||
$scope.questAccept = function(party){
|
||||
Groups.questAccept(party);
|
||||
};
|
||||
|
||||
$scope.questReject = function(party){
|
||||
Groups.questReject(party);
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -124,7 +124,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
|
||||
// Otherwise use the proper $modal.open
|
||||
$rootScope.openModal = function(template, options){//controller, scope, keyboard, backdrop){
|
||||
if (!options) options = {};
|
||||
if (options.track) Analytics.track(_.merge(options.track,{'hitType':'event','eventCategory':'button','eventAction':'click'}));
|
||||
if (options.track) Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':options.track});
|
||||
if(template === 'newStuff') return forceLoadBailey(template, options);
|
||||
return $modal.open({
|
||||
templateUrl: 'modals/' + template + '.html',
|
||||
|
||||
@@ -58,22 +58,25 @@ habitrpg.controller('SettingsCtrl',
|
||||
Guide.goto('intro', 0, true);
|
||||
}
|
||||
|
||||
$scope.showClassesTour = function(){
|
||||
Guide.goto('classes', 0, true);
|
||||
}
|
||||
|
||||
$scope.showBailey = function(){
|
||||
User.set({'flags.newStuff':true});
|
||||
}
|
||||
|
||||
$scope.saveDayStart = function(){
|
||||
var dayStart = +User.user.preferences.dayStart;
|
||||
if (_.isNaN(dayStart) || dayStart < 0 || dayStart > 24) {
|
||||
dayStart = 0;
|
||||
return alert(window.env.t('enterNumber'));
|
||||
}
|
||||
User.set({'preferences.dayStart': dayStart});
|
||||
}
|
||||
$scope.dayStart = User.user.preferences.dayStart;
|
||||
|
||||
$scope.openDayStartModal = function(dayStart) {
|
||||
$scope.dayStart = +dayStart;
|
||||
$scope.nextCron = _calculateNextCron();
|
||||
|
||||
$rootScope.openModal('change-day-start', { scope: $scope });
|
||||
};
|
||||
|
||||
$scope.saveDayStart = function() {
|
||||
User.set({
|
||||
'preferences.dayStart': Math.floor($scope.dayStart),
|
||||
'lastCron': +new Date
|
||||
});
|
||||
};
|
||||
|
||||
$scope.language = window.env.language;
|
||||
$scope.avalaibleLanguages = window.env.avalaibleLanguages;
|
||||
@@ -196,5 +199,18 @@ habitrpg.controller('SettingsCtrl',
|
||||
subs["google_6mo"].discount = false;
|
||||
});
|
||||
}
|
||||
|
||||
function _calculateNextCron() {
|
||||
$scope.dayStart;
|
||||
|
||||
var nextCron = moment().hours($scope.dayStart).minutes(0).seconds(0).milliseconds(0);
|
||||
|
||||
var currentHour = moment().format('H');
|
||||
if (currentHour >= $scope.dayStart) {
|
||||
nextCron = nextCron.add(1, 'day');;
|
||||
}
|
||||
|
||||
return +nextCron.format('x');
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller("TavernCtrl", ['$scope', 'Groups', 'User',
|
||||
function($scope, Groups, User) {
|
||||
$scope.group = Groups.tavern();
|
||||
$scope.toggleUserTier = function($event) {
|
||||
$($event.target).next().toggle();
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -43,6 +43,10 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$
|
||||
$scope._editing.profile = false;
|
||||
}
|
||||
|
||||
$scope.acknowledgeHealthWarning = function(){
|
||||
User.user.ops.update && User.set({'flags.warnedLowHealth':true});
|
||||
}
|
||||
|
||||
/**
|
||||
* For gem-unlockable preferences, (a) if owned, select preference (b) else, purchase
|
||||
* @param path: User.preferences <-> User.purchased maps like User.preferences.skin=abc <-> User.purchased.skin.abc.
|
||||
|
||||
@@ -1,110 +1,139 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Services that persists and retrieves user from localStorage.
|
||||
*/
|
||||
(function() {
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.factory('Groups', groupsFactory);
|
||||
|
||||
angular.module('habitrpg').factory('Groups',
|
||||
['$rootScope','ApiUrl', '$resource', '$q', '$http', 'User', 'Challenges', 'Analytics', '$location',
|
||||
function($rootScope, ApiUrl, $resource, $q, $http, User, Challenges, Analytics, $location) {
|
||||
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);
|
||||
});
|
||||
return data;
|
||||
}
|
||||
},
|
||||
groupsFactory.$inject = [
|
||||
'$location',
|
||||
'$resource',
|
||||
'$rootScope',
|
||||
'Analytics',
|
||||
'ApiUrl',
|
||||
'Challenges',
|
||||
'User'
|
||||
];
|
||||
|
||||
syncParty: {method: "GET", url: '/api/v2/groups/party'},
|
||||
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'}
|
||||
});
|
||||
function groupsFactory($location, $resource, $rootScope, Analytics, ApiUrl, Challenges, User) {
|
||||
|
||||
// Defer loading everything until they're requested
|
||||
var data = {party: undefined, myGuilds: undefined, publicGuilds: undefined, tavern: undefined};
|
||||
var data = {party: undefined, myGuilds: undefined, publicGuilds: undefined, tavern: undefined};
|
||||
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) {
|
||||
data = angular.fromJson(data);
|
||||
_.each(data && data.challenges, function(c) {
|
||||
angular.extend(c, Challenges.Challenge.prototype);
|
||||
});
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
var syncUser = function(res) {
|
||||
User.sync();
|
||||
}
|
||||
var logError = function(err) {
|
||||
console.log(err);
|
||||
}
|
||||
syncParty: {method: "GET", url: '/api/v2/groups/party'},
|
||||
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'},
|
||||
questLeave: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questLeave'}
|
||||
});
|
||||
|
||||
return {
|
||||
party: function(cb){
|
||||
function _syncUser() {
|
||||
User.sync();
|
||||
}
|
||||
|
||||
function _logError(err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
function party(cb) {
|
||||
if (!data.party) return (data.party = Group.get({gid: 'party'}, cb));
|
||||
return (cb) ? cb(party) : data.party;
|
||||
},
|
||||
publicGuilds: function(){
|
||||
}
|
||||
|
||||
function publicGuilds() {
|
||||
//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(){
|
||||
}
|
||||
|
||||
function myGuilds() {
|
||||
if (!data.myGuilds) data.myGuilds = Group.query({type:'guilds'});
|
||||
return data.myGuilds;
|
||||
},
|
||||
tavern: function(){
|
||||
}
|
||||
|
||||
function tavern() {
|
||||
if (!data.tavern) data.tavern = Group.get({gid:'habitrpg'});
|
||||
return data.tavern;
|
||||
},
|
||||
}
|
||||
|
||||
questAccept: function(party){
|
||||
function questAccept(party) {
|
||||
Analytics.updateUser({'partyID':party.id,'partySize':party.memberCount});
|
||||
party.$questAccept()
|
||||
.then(syncUser, logError);
|
||||
},
|
||||
return party.$questAccept()
|
||||
.then(_syncUser, _logError);
|
||||
}
|
||||
|
||||
questReject: function(party){
|
||||
function questReject(party) {
|
||||
Analytics.updateUser({'partyID':party.id,'partySize':party.memberCount});
|
||||
party.$questReject()
|
||||
.then(syncUser, logError);
|
||||
},
|
||||
return party.$questReject()
|
||||
.then(_syncUser, _logError);
|
||||
}
|
||||
|
||||
questCancel: function(party){
|
||||
function questCancel(party) {
|
||||
Analytics.updateUser({'partyID':party.id,'partySize':party.memberCount});
|
||||
party.$questCancel()
|
||||
.then(syncUser, logError);
|
||||
},
|
||||
return party.$questCancel()
|
||||
.then(_syncUser, _logError);
|
||||
}
|
||||
|
||||
questAbort: function(party){
|
||||
function questAbort(party) {
|
||||
Analytics.updateUser({'partyID':party.id,'partySize':party.memberCount});
|
||||
party.$questAbort()
|
||||
.then(syncUser, logError);
|
||||
},
|
||||
return party.$questAbort()
|
||||
.then(_syncUser, _logError);
|
||||
}
|
||||
|
||||
inviteOrStartParty: function(group) {
|
||||
if (group.type === "party") {
|
||||
$rootScope.openModal('invite-friends', {
|
||||
function questLeave(party) {
|
||||
Analytics.updateUser({'partyID':party.id,'partySize':party.memberCount});
|
||||
return party.$questLeave()
|
||||
.then(_syncUser, _logError);
|
||||
}
|
||||
|
||||
function inviteOrStartParty(group) {
|
||||
Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Invite Friends'});
|
||||
if (group.type === "party" || $location.$$path === "/options/groups/party") {
|
||||
group.type = 'party';
|
||||
$rootScope.openModal('invite-party', {
|
||||
controller:'InviteToGroupCtrl',
|
||||
resolve: {
|
||||
injectedGroup: function(){ return group; }
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Invite Friends'});
|
||||
$location.path("/options/groups/party");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// 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,
|
||||
return {
|
||||
party: party,
|
||||
publicGuilds: publicGuilds,
|
||||
myGuilds: myGuilds,
|
||||
tavern: tavern,
|
||||
questAccept: questAccept,
|
||||
questReject: questReject,
|
||||
questAbort: questAbort,
|
||||
questLeave: questLeave,
|
||||
questCancel: questCancel,
|
||||
inviteOrStartParty: inviteOrStartParty,
|
||||
|
||||
Group: Group
|
||||
data: data,
|
||||
Group: Group
|
||||
}
|
||||
}
|
||||
}])
|
||||
})();
|
||||
|
||||
@@ -40,3 +40,20 @@ window.habitrpg = angular.module('habitrpg', ['chieffancypants.loadingBar', 'ui.
|
||||
});
|
||||
});
|
||||
}])
|
||||
|
||||
.controller('AccordionCtrl', function() {
|
||||
function openHashAccordion() {
|
||||
if (window.location.hash) {
|
||||
var $target = $(window.location.hash.replace('/',''));
|
||||
if ($target.hasClass('collapse')) {
|
||||
$target.collapse('show');
|
||||
$('html, body').animate({
|
||||
scrollTop: $($target).offset().top - 100
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
$(document).ready(function(){
|
||||
openHashAccordion();
|
||||
});
|
||||
})
|
||||
|
||||
@@ -71,20 +71,27 @@
|
||||
"js/directives/when-scrolled.directive.js",
|
||||
|
||||
"js/controllers/authCtrl.js",
|
||||
"js/controllers/menuCtrl.js",
|
||||
"js/controllers/autoCompleteCtrl.js",
|
||||
"js/controllers/challengesCtrl.js",
|
||||
"js/controllers/chatCtrl.js",
|
||||
"js/controllers/copyMessageModalCtrl.js",
|
||||
"js/controllers/filtersCtrl.js",
|
||||
"js/controllers/footerCtrl.js",
|
||||
"js/controllers/groupsCtrl.js",
|
||||
"js/controllers/guildsCtrl.js",
|
||||
"js/controllers/hallCtrl.js",
|
||||
"js/controllers/headerCtrl.js",
|
||||
"js/controllers/inventoryCtrl.js",
|
||||
"js/controllers/inviteToGroupCtrl.js",
|
||||
"js/controllers/memberModalCtrl.js",
|
||||
"js/controllers/menuCtrl.js",
|
||||
"js/controllers/notificationCtrl.js",
|
||||
"js/controllers/partyCtrl.js",
|
||||
"js/controllers/rootCtrl.js",
|
||||
"js/controllers/settingsCtrl.js",
|
||||
"js/controllers/headerCtrl.js",
|
||||
"js/controllers/tavernCtrl.js",
|
||||
"js/controllers/tasksCtrl.js",
|
||||
"js/controllers/filtersCtrl.js",
|
||||
"js/controllers/userCtrl.js",
|
||||
"js/controllers/groupsCtrl.js",
|
||||
"js/controllers/inventoryCtrl.js",
|
||||
"js/controllers/footerCtrl.js",
|
||||
"js/controllers/challengesCtrl.js",
|
||||
"js/controllers/hallCtrl.js"
|
||||
"js/controllers/userCtrl.js"
|
||||
],
|
||||
"css": [
|
||||
"bower_components/bootstrap/dist/css/bootstrap.css",
|
||||
|
||||
@@ -6,6 +6,7 @@ var async = require('async');
|
||||
var utils = require('../utils');
|
||||
var nconf = require('nconf');
|
||||
var request = require('request');
|
||||
var FirebaseTokenGenerator = require('firebase-token-generator');
|
||||
var User = require('../models/user').model;
|
||||
var EmailUnsubscription = require('../models/emailUnsubscription').model;
|
||||
var analytics = utils.analytics;
|
||||
@@ -33,7 +34,7 @@ api.auth = function(req, res, next) {
|
||||
var uid = req.headers['x-api-user'];
|
||||
var token = req.headers['x-api-key'];
|
||||
if (!(uid && token)) return res.json(401, NO_TOKEN_OR_UID);
|
||||
User.findOne({_id: uid,apiToken: token}, function(err, user) {
|
||||
User.findOne({_id: uid, apiToken: token}, function(err, user) {
|
||||
if (err) return next(err);
|
||||
if (_.isEmpty(user)) return res.json(401, NO_USER_FOUND);
|
||||
if (user.auth.blocked) return res.json(401, accountSuspended(user._id));
|
||||
@@ -319,11 +320,32 @@ api.changePassword = function(req, res, next) {
|
||||
if (err) next(err);
|
||||
res.send(200);
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
var firebaseTokenGeneratorInstance = new FirebaseTokenGenerator(nconf.get('FIREBASE:SECRET'));
|
||||
api.getFirebaseToken = function(req, res, next) {
|
||||
var user = res.locals.user;
|
||||
// Expires 24 hours after now (60*60*24*1000) (in milliseconds)
|
||||
var expires = new Date();
|
||||
expires.setTime(expires.getTime() + 86400000);
|
||||
|
||||
var token = firebaseTokenGeneratorInstance
|
||||
.createToken({
|
||||
uid: user._id,
|
||||
isHabiticaUser: true
|
||||
}, {
|
||||
expires: expires
|
||||
});
|
||||
|
||||
res.json(200, {
|
||||
token: token,
|
||||
expires: expires
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Registers a new user. Only accepting username/password registrations, no Facebook
|
||||
*/
|
||||
*/
|
||||
|
||||
api.setupPassport = function(router) {
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ function clone(a) {
|
||||
var _ = require('lodash');
|
||||
var nconf = require('nconf');
|
||||
var async = require('async');
|
||||
var Q = require('q');
|
||||
var utils = require('./../utils');
|
||||
var shared = require('../../../common');
|
||||
var User = require('./../models/user').model;
|
||||
@@ -18,6 +19,7 @@ var isProd = nconf.get('NODE_ENV') === 'production';
|
||||
var api = module.exports;
|
||||
var pushNotify = require('./pushNotifications');
|
||||
var analytics = utils.analytics;
|
||||
var firebase = require('../libs/firebase');
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------
|
||||
@@ -190,7 +192,11 @@ api.create = function(req, res, next) {
|
||||
async.waterfall([
|
||||
function(cb){user.save(cb)},
|
||||
function(saved,ct,cb){group.save(cb)},
|
||||
function(saved,ct,cb){saved.populate('members',nameFields,cb)}
|
||||
function(saved,ct,cb){
|
||||
firebase.updateGroupData(saved);
|
||||
firebase.addUserToGroup(saved._id, user._id);
|
||||
saved.populate('members', nameFields, cb);
|
||||
}
|
||||
],function(err,saved){
|
||||
if (err) return next(err);
|
||||
res.json(saved);
|
||||
@@ -207,6 +213,8 @@ api.create = function(req, res, next) {
|
||||
group.save(cb);
|
||||
},
|
||||
function(saved, count, cb){
|
||||
firebase.updateGroupData(saved);
|
||||
firebase.addUserToGroup(saved._id, user._id);
|
||||
saved.populate('members', nameFields, cb);
|
||||
}
|
||||
], function(err, populated){
|
||||
@@ -231,10 +239,13 @@ api.update = function(req, res, next) {
|
||||
|
||||
group.save(function(err, saved){
|
||||
if (err) return next(err);
|
||||
|
||||
firebase.updateGroupData(saved);
|
||||
res.send(204);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO remove from api object?
|
||||
api.attachGroup = function(req, res, next) {
|
||||
var gid = req.params.gid;
|
||||
var q = (gid == 'party') ? Group.findOne({type: 'party', members: {'$in': [res.locals.user._id]}}) : Group.findById(gid);
|
||||
@@ -243,7 +254,7 @@ api.attachGroup = function(req, res, next) {
|
||||
if(!group) return res.json(404, {err: "Group not found"});
|
||||
res.locals.group = group;
|
||||
next();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
api.getChat = function(req, res, next) {
|
||||
@@ -462,7 +473,12 @@ api.join = function(req, res, next) {
|
||||
if(!isUserInvited) return res.json(401, {err: "Can't join a group you're not invited to."});
|
||||
|
||||
if (!_.contains(group.members, user._id)){
|
||||
if (group.members.length === 0) {
|
||||
group.leader = user._id;
|
||||
}
|
||||
|
||||
group.members.push(user._id);
|
||||
|
||||
if (group.invites.length > 0) {
|
||||
group.invites.splice(_.indexOf(group.invites, user._id), 1);
|
||||
}
|
||||
@@ -473,6 +489,8 @@ api.join = function(req, res, next) {
|
||||
group.save(cb);
|
||||
},
|
||||
function(cb){
|
||||
firebase.addUserToGroup(group._id, user._id);
|
||||
// TODO why query group once again?
|
||||
populateQuery(group.type, Group.findById(group._id)).exec(cb);
|
||||
}
|
||||
], function(err, results){
|
||||
@@ -484,81 +502,22 @@ api.join = function(req, res, next) {
|
||||
}
|
||||
|
||||
api.leave = function(req, res, next) {
|
||||
var user = res.locals.user,
|
||||
group = res.locals.group;
|
||||
var user = res.locals.user;
|
||||
var group = res.locals.group;
|
||||
|
||||
if (group.type === 'party' && user.party.quest && user.party.quest.key) {
|
||||
return res.json(403, 'You cannot leave party during an active quest. Please leave the quest first');
|
||||
}
|
||||
|
||||
// When removing the user from challenges, should we keep the tasks?
|
||||
var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all';
|
||||
async.parallel([
|
||||
// Remove active quest from user if they're leaving the party
|
||||
function(cb){
|
||||
if (group.type != 'party') return cb(null,{},1);
|
||||
user.party.quest = Group.cleanQuestProgress();
|
||||
user.save(cb);
|
||||
},
|
||||
// Remove user from group challenges
|
||||
function(cb){
|
||||
async.waterfall([
|
||||
// Find relevant challenges
|
||||
function(cb2) {
|
||||
Challenge.find({
|
||||
_id: {$in: user.challenges}, // Challenges I am in
|
||||
group: group._id // that belong to the group I am leaving
|
||||
}, cb2);
|
||||
},
|
||||
// Update each challenge
|
||||
function(challenges, cb2) {
|
||||
Challenge.update(
|
||||
{_id:{$in: _.pluck(challenges, '_id')}},
|
||||
{$pull:{members:user._id}},
|
||||
{multi: true},
|
||||
function(err) {
|
||||
cb2(err, challenges); // pass `challenges` above to cb
|
||||
}
|
||||
);
|
||||
},
|
||||
// Unlink the challenge tasks from user
|
||||
function(challenges, cb2) {
|
||||
async.waterfall(challenges.map(function(chal) {
|
||||
return function(cb3) {
|
||||
var i = user.challenges.indexOf(chal._id)
|
||||
if (~i) user.challenges.splice(i,1);
|
||||
user.unlink({cid:chal._id, keep:keep}, cb3);
|
||||
}
|
||||
}), cb2);
|
||||
}
|
||||
], cb);
|
||||
},
|
||||
// Update the group
|
||||
function(cb){
|
||||
var update = {$pull:{members:user._id}};
|
||||
if (group.type == 'party' && group.quest.key){
|
||||
update['$unset'] = {};
|
||||
update['$unset']['quest.members.' + user._id] = 1;
|
||||
}
|
||||
// FIXME do we want to remove the group `if group.members.length == 0` ? (well, 1 since the update hasn't gone through yet)
|
||||
if (group.members.length > 1) {
|
||||
var seniorMember = _.find(group.members, function (m) {return m != user._id});
|
||||
// If the leader is leaving (or if the leader previously left, and this wasn't accounted for)
|
||||
var leader = group.leader;
|
||||
if (leader == user._id || !~group.members.indexOf(leader)) {
|
||||
update['$set'] = update['$set'] || {};
|
||||
update['$set'].leader = seniorMember;
|
||||
}
|
||||
leader = group.quest && group.quest.leader;
|
||||
if (leader && (leader == user._id || !~group.members.indexOf(leader))) {
|
||||
update['$set'] = update['$set'] || {};
|
||||
update['$set']['quest.leader'] = seniorMember;
|
||||
}
|
||||
}
|
||||
update['$inc'] = {memberCount: -1};
|
||||
Group.update({_id:group._id},update,cb);
|
||||
}
|
||||
],function(err){
|
||||
|
||||
group.leave(user, keep, function(err){
|
||||
if (err) return next(err);
|
||||
user = group = keep = null;
|
||||
return res.send(204);
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var inviteByUUIDs = function(uuids, group, req, res, next){
|
||||
async.each(uuids, function(uuid, cb){
|
||||
@@ -568,17 +527,18 @@ var inviteByUUIDs = function(uuids, group, req, res, next){
|
||||
return cb({code:400,err:'User with id "' + uuid + '" not found'});
|
||||
if (group.type == 'guild') {
|
||||
if (_.contains(group.members,uuid))
|
||||
return cb({code:400,err: "User already in that group"});
|
||||
return cb({code:400, err: "User already in that group"});
|
||||
if (invite.invitations && invite.invitations.guilds && _.find(invite.invitations.guilds, {id:group._id}))
|
||||
return cb({code:400,err:"User already invited to that group"});
|
||||
return cb({code:400, err:"User already invited to that group"});
|
||||
sendInvite();
|
||||
} else if (group.type == 'party') {
|
||||
if (invite.invitations && !_.isEmpty(invite.invitations.party))
|
||||
return cb({code:400,err:"User already pending invitation."});
|
||||
Group.find({type:'party', members:{$in:[uuid]}}, function(err, groups){
|
||||
return cb({code: 400,err:"User already pending invitation."});
|
||||
Group.find({type: 'party', members: {$in: [uuid]}}, function(err, groups){
|
||||
if (err) return cb(err);
|
||||
if (!_.isEmpty(groups))
|
||||
return cb({code:400,err:"User already in a party."})
|
||||
if (!_.isEmpty(groups) && groups[0].members.length > 1) {
|
||||
return cb({code: 400, err: "User already in a party."})
|
||||
}
|
||||
sendInvite();
|
||||
});
|
||||
}
|
||||
@@ -600,9 +560,6 @@ var inviteByUUIDs = function(uuids, group, req, res, next){
|
||||
async.series([
|
||||
function(cb){
|
||||
invite.save(cb);
|
||||
},
|
||||
function(cb){
|
||||
group.save(cb);
|
||||
}
|
||||
], function(err, results){
|
||||
if (err) return cb(err);
|
||||
@@ -636,14 +593,20 @@ var inviteByUUIDs = function(uuids, group, req, res, next){
|
||||
}, function(err){
|
||||
if(err) return err.code ? res.json(err.code, {err: err.err}) : next(err);
|
||||
|
||||
// TODO pass group from save above don't find it again, or you have to find it again in order to run populate?
|
||||
populateQuery(group.type, Group.findById(group._id)).exec(function(err, populatedGroup){
|
||||
if(err) return next(err);
|
||||
async.series([
|
||||
function(cb) {
|
||||
group.save(cb);
|
||||
},
|
||||
function(cb) {
|
||||
// TODO pass group from save above don't find it again, or you have to find it again in order to run populate?
|
||||
populateQuery(group.type, Group.findById(group._id)).exec(function(err, populatedGroup){
|
||||
if(err) return next(err);
|
||||
|
||||
res.json(populatedGroup);
|
||||
});
|
||||
res.json(populatedGroup);
|
||||
});
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
var inviteByEmails = function(invites, group, req, res, next){
|
||||
@@ -664,6 +627,7 @@ var inviteByEmails = function(invites, group, req, res, next){
|
||||
}
|
||||
|
||||
// yeah, it supports guild too but for backward compatibility we'll use partyInvite as query
|
||||
|
||||
var link = '?partyInvite='+ utils.encrypt(JSON.stringify({id:group._id, inviter:res.locals.user._id, name:group.name}));
|
||||
|
||||
var inviterVars = utils.getUserInfo(res.locals.user, ['name', 'email']);
|
||||
@@ -694,9 +658,9 @@ var inviteByEmails = function(invites, group, req, res, next){
|
||||
}, function(err){
|
||||
if(err) return err.code ? res.json(err.code, {err: err.err}) : next(err);
|
||||
|
||||
if(usersAlreadyRegistered.length > 0){
|
||||
if (usersAlreadyRegistered.length > 0){
|
||||
inviteByUUIDs(usersAlreadyRegistered, group, req, res, next);
|
||||
}else{
|
||||
} else{
|
||||
|
||||
// Send only status code down the line because it doesn't need
|
||||
// info on invited users since they are not yet registered
|
||||
@@ -708,11 +672,11 @@ var inviteByEmails = function(invites, group, req, res, next){
|
||||
api.invite = function(req, res, next){
|
||||
var group = res.locals.group;
|
||||
|
||||
if(req.body.uuids){
|
||||
if (req.body.uuids) {
|
||||
inviteByUUIDs(req.body.uuids, group, req, res, next);
|
||||
}else if(req.body.emails){
|
||||
} else if (req.body.emails) {
|
||||
inviteByEmails(req.body.emails, group, req, res, next)
|
||||
}else{
|
||||
} else {
|
||||
return res.json(400,{err: "Can invite only by email or uuid"});
|
||||
}
|
||||
}
|
||||
@@ -739,6 +703,10 @@ api.removeMember = function(req, res, next){
|
||||
return res.json(401, {err: "Only group leader can remove a member!"});
|
||||
}
|
||||
|
||||
if(user._id === uuid){
|
||||
return res.json(401, {err: "You cannot remove yourself!"});
|
||||
}
|
||||
|
||||
if(_.contains(group.members, uuid)){
|
||||
var update = {$pull:{members:uuid}};
|
||||
if(group.quest && group.quest.members){
|
||||
@@ -1068,3 +1036,37 @@ api.questAbort = function(req, res, next){
|
||||
group = null;
|
||||
})
|
||||
}
|
||||
|
||||
api.questLeave = function(req, res, next) {
|
||||
// Non-member leave quest while still in progress
|
||||
var group = res.locals.group;
|
||||
var user = res.locals.user;
|
||||
|
||||
if (!(group.quest && group.quest.active)) {
|
||||
return res.json(404, { err: 'No active quest to leave' });
|
||||
}
|
||||
|
||||
if (!(group.quest.members && group.quest.members[user._id])) {
|
||||
return res.json(403, { err: 'You are not part of the quest' });
|
||||
}
|
||||
|
||||
if (group.quest.leader === user._id) {
|
||||
return res.json(403, { err: 'Quest leader cannot leave quest' });
|
||||
}
|
||||
|
||||
delete group.quest.members[user._id];
|
||||
group.markModified('quest.members');
|
||||
|
||||
user.party.quest = Group.cleanQuestProgress();
|
||||
user.markModified('party.quest');
|
||||
|
||||
var groupSavePromise = Q.nbind(group.save, group);
|
||||
var userSavePromise = Q.nbind(user.save, user);
|
||||
|
||||
Q.all([groupSavePromise(), userSavePromise()])
|
||||
.done(function(values) {
|
||||
return res.send(204);
|
||||
}, function(error) {
|
||||
return next(error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ var logging = require('./../logging');
|
||||
var acceptablePUTPaths;
|
||||
var api = module.exports;
|
||||
var qs = require('qs');
|
||||
var firebase = require('../libs/firebase');
|
||||
var webhook = require('../webhook');
|
||||
|
||||
// api.purchase // Shared.ops
|
||||
@@ -365,13 +366,33 @@ api.cron = function(req, res, next) {
|
||||
// api.reset // Shared.ops
|
||||
|
||||
api['delete'] = function(req, res, next) {
|
||||
var plan = res.locals.user.purchased.plan;
|
||||
if (plan && plan.customerId && !plan.dateTerminated)
|
||||
var user = res.locals.user;
|
||||
var plan = user.purchased.plan;
|
||||
|
||||
if (plan && plan.customerId && !plan.dateTerminated){
|
||||
return res.json(400,{err:"You have an active subscription, cancel your plan before deleting your account."});
|
||||
res.locals.user.remove(function(err){
|
||||
if (err) return next(err);
|
||||
res.send(200);
|
||||
})
|
||||
}
|
||||
|
||||
Group.find({
|
||||
members: {
|
||||
'$in': [user._id]
|
||||
}
|
||||
}, function(err, groups){
|
||||
if(err) return next(err);
|
||||
|
||||
async.each(groups, function(group, cb){
|
||||
group.leave(user, 'remove-all', cb);
|
||||
}, function(err){
|
||||
if(err) return next(err);
|
||||
|
||||
user.remove(function(err){
|
||||
if(err) return next(err);
|
||||
|
||||
firebase.deleteUser(user._id);
|
||||
res.send(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
var Firebase = require('firebase');
|
||||
var nconf = require('nconf');
|
||||
var isProd = nconf.get('NODE_ENV') === 'production';
|
||||
var firebaseConfig = nconf.get('FIREBASE');
|
||||
|
||||
var firebaseRef;
|
||||
var isFirebaseEnabled = (nconf.get('NODE_ENV') === 'production') && (firebaseConfig.ENABLED === 'true');
|
||||
|
||||
// Setup
|
||||
if(isFirebaseEnabled){
|
||||
firebaseRef = new Firebase('https://' + firebaseConfig.APP + '.firebaseio.com');
|
||||
|
||||
// TODO what happens if an op is sent before client is authenticated?
|
||||
firebaseRef.authWithCustomToken(firebaseConfig.SECRET, function(err, authData){
|
||||
// TODO it's ok to kill the server here? what if FB is offline?
|
||||
if(err) throw new Error('Impossible to authenticate Firebase');
|
||||
});
|
||||
}
|
||||
|
||||
var api = module.exports = {};
|
||||
|
||||
api.updateGroupData = function(group){
|
||||
if(!isFirebaseEnabled) return;
|
||||
// TODO is throw ok? we don't have callbacks
|
||||
if(!group) throw new Error('group is required.');
|
||||
// Return in case of tavern (comparison working because we use string for _id)
|
||||
if(group._id === 'habitrpg') return;
|
||||
|
||||
firebaseRef.child('rooms/' + group._id)
|
||||
.set({
|
||||
name: group.name
|
||||
});
|
||||
};
|
||||
|
||||
api.addUserToGroup = function(groupId, userId){
|
||||
if(!isFirebaseEnabled) return;
|
||||
if(!userId || !groupId) throw new Error('groupId, userId are required.');
|
||||
if(groupId === 'habitrpg') return;
|
||||
|
||||
firebaseRef.child('members/' + groupId + '/' + userId)
|
||||
.set(true);
|
||||
|
||||
firebaseRef.child('users/' + userId + '/rooms/' + groupId)
|
||||
.set(true);
|
||||
};
|
||||
|
||||
api.removeUserFromGroup = function(groupId, userId){
|
||||
if(!isFirebaseEnabled) return;
|
||||
if(!userId || !groupId) throw new Error('groupId, userId are required.');
|
||||
if(groupId === 'habitrpg') return;
|
||||
|
||||
firebaseRef.child('members/' + groupId + '/' + userId)
|
||||
.remove();
|
||||
|
||||
firebaseRef.child('users/' + userId + '/rooms/' + groupId)
|
||||
.remove();
|
||||
};
|
||||
|
||||
api.deleteGroup = function(groupId){
|
||||
if(!isFirebaseEnabled) return;
|
||||
if(!groupId) throw new Error('groupId is required.');
|
||||
if(groupId === 'habitrpg') return;
|
||||
|
||||
firebaseRef.child('rooms/' + groupId)
|
||||
.remove();
|
||||
|
||||
// FIXME not really necessary as long as we only store room data,
|
||||
// as empty objects are automatically deleted (/members/... in future...)
|
||||
firebaseRef.child('members/' + groupId)
|
||||
.remove();
|
||||
};
|
||||
|
||||
// FIXME not really necessary as long as we only store room data,
|
||||
// as empty objects are automatically deleted
|
||||
api.deleteUser = function(userId){
|
||||
if(!isFirebaseEnabled) return;
|
||||
if(!userId) throw new Error('userId is required.');
|
||||
|
||||
firebaseRef.child('users/' + userId)
|
||||
.remove();
|
||||
};
|
||||
+104
-4
@@ -4,7 +4,11 @@ var shared = require('../../../common');
|
||||
var _ = require('lodash');
|
||||
var async = require('async');
|
||||
var logging = require('../logging');
|
||||
var Challenge = require('./../models/challenge').model;
|
||||
var firebase = require('../libs/firebase');
|
||||
|
||||
// NOTE any change to groups' members in MongoDB will have to be run through the API
|
||||
// changes made directly to the db will cause Firebase to get out of sync
|
||||
var GroupSchema = new Schema({
|
||||
_id: {type: String, 'default': shared.uuid},
|
||||
name: String,
|
||||
@@ -239,7 +243,8 @@ process.nextTick(function(){
|
||||
// Using _assign so we don't lose the reference to the exported tavern
|
||||
_.assign(module.exports.tavern, tavern);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
GroupSchema.statics.tavernBoss = function(user,progress) {
|
||||
if (!progress) return;
|
||||
|
||||
@@ -342,6 +347,100 @@ GroupSchema.statics.bossQuest = function(user, progress, cb) {
|
||||
})
|
||||
}
|
||||
|
||||
// Remove user from this group
|
||||
GroupSchema.methods.leave = function(user, keep, mainCb){
|
||||
if(!user) return mainCb(new Error('Missing user.'));
|
||||
|
||||
if(keep && typeof keep === 'function'){
|
||||
mainCb = keep;
|
||||
keep = null;
|
||||
}
|
||||
if(typeof keep !== 'string') keep = 'keep-all'; // can be also 'remove-all'
|
||||
|
||||
var group = this;
|
||||
var groupWasRemoved = false;
|
||||
|
||||
async.parallel([
|
||||
// Remove user from group challenges
|
||||
function(cb){
|
||||
async.waterfall([
|
||||
// Find relevant challenges
|
||||
function(cb2) {
|
||||
Challenge.find({
|
||||
_id: {$in: user.challenges}, // Challenges I am in
|
||||
group: group._id // that belong to the group I am leaving
|
||||
}, cb2);
|
||||
},
|
||||
|
||||
// Update each challenge
|
||||
function(challenges, cb2) {
|
||||
Challenge.update(
|
||||
{_id: {$in: _.pluck(challenges, '_id')}},
|
||||
{$pull: {members: user._id}},
|
||||
{multi: true},
|
||||
function(err) {
|
||||
cb2(err, challenges); // pass `challenges` above to cb
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// Unlink the challenge tasks from user
|
||||
function(challenges, cb2) {
|
||||
async.waterfall(challenges.map(function(chal) {
|
||||
return function(cb3) {
|
||||
var i = user.challenges.indexOf(chal._id)
|
||||
if (~i) user.challenges.splice(i,1);
|
||||
user.unlink({cid: chal._id, keep: keep}, cb3);
|
||||
}
|
||||
}), cb2);
|
||||
}
|
||||
], cb);
|
||||
},
|
||||
|
||||
// Update the group
|
||||
function(cb){
|
||||
// If user is the last one in group and group is private, delete it
|
||||
if(group.members.length === 1 && (
|
||||
group.type === 'party' ||
|
||||
(group.type === 'guild' && group.privacy === 'private')
|
||||
)){
|
||||
// TODO remove invitations to this group
|
||||
groupWasRemoved = true;
|
||||
Group.remove({
|
||||
_id: group._id
|
||||
}, cb);
|
||||
}else{ // otherwise just remove a member
|
||||
var update = {$pull: {members: user._id}};
|
||||
|
||||
// If the leader is leaving (or if the leader previously left, and this wasn't accounted for)
|
||||
var leader = group.leader;
|
||||
|
||||
if(leader == user._id || !~group.members.indexOf(leader)){
|
||||
var seniorMember = _.find(group.members, function (m) {return m != user._id});
|
||||
|
||||
// could not exist in case of public guild with 1 member who is leaving
|
||||
if(seniorMember){
|
||||
if (leader == user._id || !~group.members.indexOf(leader)) {
|
||||
update['$set'] = update['$set'] || {};
|
||||
update['$set'].leader = seniorMember;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update['$inc'] = {memberCount: -1};
|
||||
Group.update({_id: group._id}, update, cb);
|
||||
}
|
||||
}
|
||||
], function(err){
|
||||
if(err) return mainCb(err);
|
||||
|
||||
firebase.removeUserFromGroup(group._id, user._id);
|
||||
if(groupWasRemoved) firebase.deleteGroup(group._id);
|
||||
return mainCb();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
GroupSchema.methods.toJSON = function() {
|
||||
var doc = this.toObject();
|
||||
if(doc.chat){
|
||||
@@ -358,14 +457,15 @@ module.exports.schema = GroupSchema;
|
||||
var Group = module.exports.model = mongoose.model("Group", GroupSchema);
|
||||
|
||||
// initialize tavern if !exists (fresh installs)
|
||||
Group.count({_id:'habitrpg'},function(err,ct){
|
||||
Group.count({_id: 'habitrpg'}, function(err, ct){
|
||||
if (ct > 0) return;
|
||||
|
||||
new Group({
|
||||
_id: 'habitrpg',
|
||||
chat: [],
|
||||
leader: '9',
|
||||
name: 'HabitRPG',
|
||||
type: 'guild',
|
||||
privacy:'public'
|
||||
privacy: 'public'
|
||||
}).save();
|
||||
})
|
||||
});
|
||||
@@ -163,7 +163,8 @@ var UserSchema = new Schema({
|
||||
armoireEnabled: {type: Boolean, 'default': false},
|
||||
armoireOpened: {type: Boolean, 'default': false},
|
||||
armoireEmpty: {type: Boolean, 'default': false},
|
||||
cardReceived: {type: Boolean, 'default': false}
|
||||
cardReceived: {type: Boolean, 'default': false},
|
||||
warnedLowHealth: {type: Boolean, 'default': false}
|
||||
},
|
||||
history: {
|
||||
exp: Array, // [{date: Date, value: Number}], // big peformance issues if these are defined
|
||||
|
||||
@@ -302,7 +302,7 @@ module.exports = (swagger, v2) ->
|
||||
method: 'POST'
|
||||
description: "Casts a spell on a target."
|
||||
parameters: [
|
||||
path 'spell',"The key of the spell to cast (see ../../common#content.coffee)",'string'
|
||||
path 'spell',"The key of the spell to cast (see ../../common#content/index.coffee)",'string'
|
||||
query 'targetType',"The type of object you're targeting",'string',['party','self','user','task']
|
||||
query 'targetId',"The ID of the object you're targeting",'string'
|
||||
|
||||
@@ -527,6 +527,14 @@ module.exports = (swagger, v2) ->
|
||||
middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup]
|
||||
action: groups.questAbort
|
||||
|
||||
"/groups/{gid}/questLeave":
|
||||
spec:
|
||||
method: 'POST'
|
||||
description: 'Leave an active quest (Quest leaders cannot leave active quests. They must abort the quest to leave)'
|
||||
parameters: [path('gid','Group to leave quest in','string')]
|
||||
middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup]
|
||||
action: groups.questLeave
|
||||
|
||||
#TODO PUT /groups/:gid/chat/:messageId
|
||||
|
||||
"/groups/{gid}/chat:GET":
|
||||
|
||||
@@ -13,6 +13,7 @@ router.post('/api/v2/user/reset-password', i18n.getUserLanguage, auth.resetPassw
|
||||
router.post('/api/v2/user/change-password', i18n.getUserLanguage, auth.auth, auth.changePassword);
|
||||
router.post('/api/v2/user/change-username', i18n.getUserLanguage, auth.auth, auth.changeUsername);
|
||||
router.post('/api/v2/user/change-email', i18n.getUserLanguage, auth.auth, auth.changeEmail);
|
||||
router.post('/api/v2/user/auth/firebase', i18n.getUserLanguage, auth.auth, auth.getFirebaseToken);
|
||||
|
||||
router.post('/api/v1/register', i18n.getUserLanguage, auth.registerUser);
|
||||
router.post('/api/v1/user/auth/local', i18n.getUserLanguage, auth.loginLocal);
|
||||
|
||||
@@ -20,11 +20,14 @@ router.get('/', i18n.getUserLanguage, locals, function(req, res) {
|
||||
|
||||
// -------- Marketing --------
|
||||
|
||||
var pages = ['front', 'privacy', 'terms', 'api', 'features', 'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines', 'old-news', 'press-kit'];
|
||||
var pages = ['front', 'privacy', 'terms', 'api', 'features', 'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines', 'old-news', 'press-kit', 'faq'];
|
||||
|
||||
_.each(pages, function(name){
|
||||
router.get('/static/' + name, i18n.getUserLanguage, locals, function(req, res) {
|
||||
res.render('static/' + name, {env: res.locals.habitrpg});
|
||||
res.render( 'static/' + name, {
|
||||
env: res.locals.habitrpg,
|
||||
marked: require('marked')
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) {
|
||||
});
|
||||
autoinc.init(db);
|
||||
|
||||
require('./libs/firebase');
|
||||
|
||||
// load schemas & models
|
||||
require('./models/challenge');
|
||||
require('./models/group');
|
||||
|
||||
@@ -1,37 +1,25 @@
|
||||
//- If this variable gets any bigger, please move it out of this jade template and into common/script/content
|
||||
- var questCategories = {'unlockable':'unlockableQuests','gold':'goldQuests','pet':'petQuests'}
|
||||
include ../../shared/mixins
|
||||
|
||||
.container-fluid
|
||||
.row: .col-xs-12
|
||||
.npc_ian.pull-left-sm.col-centered
|
||||
.popover.static-popover.fade.right.in.pull-left-sm.col-centered
|
||||
.arrow.hidden-xs
|
||||
h3.popover-title=env.t('ian')
|
||||
.popover-content
|
||||
p=env.t('ianText')
|
||||
|
||||
.row
|
||||
.col-md-6
|
||||
.clearfix
|
||||
.npc_ian.pull-left-sm.col-centered
|
||||
.popover.static-popover.fade.right.in.pull-left-sm.col-centered
|
||||
.arrow.hidden-xs
|
||||
h3.popover-title=env.t('ian')
|
||||
.popover-content
|
||||
p=env.t('ianText')
|
||||
|
||||
h3.equipment-title=env.t('yourQuests')
|
||||
menu.inventory-list
|
||||
li.customize-menu
|
||||
p.muted(ng-show='questCount < 1')=env.t('noScrolls')
|
||||
each caption,type in questCategories
|
||||
menu.pets-menu(label=env.t(caption))
|
||||
div(ng-repeat='(quest_key,points) in ownedItems(user.items.quests)', ng-init='quest = Content.quests[quest_key]', ng-if='Content.quests[quest_key].category === "#{type}"')
|
||||
button.customize-option(ng-class='lockQuest(quest) ? "inventory_quest_scroll_locked inventory_quest_scroll_{{::quest.key}}_locked locked" : "inventory_quest_scroll inventory_quest_scroll_{{::quest.key}}"',
|
||||
ng-click='showQuest(quest_key)',
|
||||
data-popover-html="{{::lockQuest(quest) ? env.t('scrollsPre') : questPopover(quest) | markdown}}",
|
||||
popover-title='{{::quest.text()}}', popover-trigger='mouseenter',
|
||||
popover-placement='right', popover-append-to-body='true')
|
||||
.badge.badge-info.stack-count {{points}}
|
||||
+ownedQuests(true,'right')
|
||||
|
||||
.col-md-6.border-left
|
||||
li.customize-menu
|
||||
h3.equipment-title=env.t('questsForSale')
|
||||
each caption,type in questCategories
|
||||
menu.pets-menu(label=env.t(caption))
|
||||
div(ng-repeat='quest in Content.questsByLevel', ng-if='quest.canBuy && quest.category === "#{type}"')
|
||||
div(ng-repeat='type in Content.userCanOwnQuestCategories')
|
||||
menu.pets-menu(label='{{env.t(type + "Quests")}}')
|
||||
div(ng-repeat='quest in Content.questsByLevel', ng-if='quest.canBuy && quest.category === type')
|
||||
button.customize-option(ng-class='lockQuest(quest) ? "inventory_quest_scroll_locked inventory_quest_scroll_{{::quest.key}}_locked locked" : "inventory_quest_scroll inventory_quest_scroll_{{::quest.key}}"',
|
||||
data-popover-html="{{::lockQuest(quest,true) ? env.t('scrollsPre') : questPopover(quest) | markdown}}",
|
||||
popover-title='{{::quest.text()}}', popover-append-to-body="true",
|
||||
|
||||
@@ -39,9 +39,14 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
|
||||
strong
|
||||
!=env.t('helpWithTranslation')
|
||||
|
||||
hr
|
||||
|
||||
.form-horizontal
|
||||
h5=env.t('dateFormat')
|
||||
select.form-control(ng-model='user.preferences.dateFormat', ng-options='DF for DF in availableFormats', ng-change='set({"preferences.dateFormat": user.preferences.dateFormat})')
|
||||
|
||||
hr
|
||||
|
||||
.checkbox
|
||||
label
|
||||
input(type='checkbox', ng-click='hideHeader() ', ng-checked='user.preferences.hideHeader!==true')
|
||||
@@ -71,20 +76,34 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
|
||||
input(type='checkbox', ng-model='user.preferences.displayInviteToPartyWhenPartyIs1', ng-change='set({"preferences.displayInviteToPartyWhenPartyIs1": user.preferences.displayInviteToPartyWhenPartyIs1 ? true : false})')
|
||||
span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('displayInviteToPartyWhenPartyIs1'))=env.t('displayInviteToPartyWhenPartyIs1')
|
||||
// button.btn.btn-default(ng-click='showTour()', popover-placement='right', popover-trigger='mouseenter', popover=env.t('restartTour'))= env.t('showTour')
|
||||
|
||||
hr
|
||||
|
||||
button.btn.btn-default(ng-click='showBailey()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('showBaileyPop'))= env.t('showBailey')
|
||||
button.btn.btn-default(ng-click='openRestoreModal()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('fixValPop'))= env.t('fixVal')
|
||||
button.btn.btn-default(ng-if='user.preferences.disableClasses==true', ng-click='user.ops.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass')
|
||||
button.btn.btn-default(ng-if='!user.preferences.disableClasses && user.flags.classSelected', ng-click='showClassesTour()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('classTourPop'))= env.t('showClass')
|
||||
|
||||
div.alert.alert-warning(style='padding:2px;margin-top:7px')
|
||||
h5=env.t('customDayStart')
|
||||
h5=env.t('customDayStartInfo1')
|
||||
a(ng-click='showCustomDayStartInfo = !showCustomDayStartInfo') {{!showCustomDayStartInfo ? env.t('showMoreMore') : env.t('showMoreLess')}}
|
||||
h5(ng-if='showCustomDayStartInfo')!=env.t('customDayStartInfo4')
|
||||
hr
|
||||
|
||||
h5=env.t('customDayStart')
|
||||
alert.alert-warning=env.t('customDayStartInfo1')
|
||||
|
||||
.form-horizontal
|
||||
.form-group
|
||||
.input-group
|
||||
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') + ')'
|
||||
.col-sm-7
|
||||
select.form-control(ng-model='dayStart')
|
||||
- var number = 0
|
||||
while number < 24
|
||||
- var value = number
|
||||
- var meridian = number < 12 ? 'AM' : 'PM'
|
||||
- var hour = number++ % 12
|
||||
option(value=value) #{hour ? hour : 12}:00 #{meridian}
|
||||
|
||||
.col-sm-5
|
||||
br.visible-xs
|
||||
button.btn.btn-block.btn-primary(ng-click='openDayStartModal(dayStart)',
|
||||
ng-disabled='dayStart == user.preferences.dayStart')
|
||||
| Save Custom Day Start
|
||||
|
||||
.personal-options.col-md-6
|
||||
.panel.panel-default
|
||||
|
||||
@@ -7,7 +7,7 @@ div.chat-form.guidelines-not-accepted(ng-if='!user.flags.communityGuidelinesAcce
|
||||
div
|
||||
button.btn.btn-warning(ng-click='acceptCommunityGuidelines()')=env.t('iAcceptCommunityGuidelines')
|
||||
.chat-buttons
|
||||
button(type="button", ng-click='sync(group)')=env.t('toolTipMsg')
|
||||
button(ng-if='group.chat.length > 0 || group.memberCount > 1', type="button", ng-click='sync(group)')=env.t('toolTipMsg')
|
||||
|
||||
form.chat-form(ng-if='user.flags.communityGuidelinesAccepted' ng-submit='postChat(group,message.content)')
|
||||
div(ng-controller='AutocompleteCtrl')
|
||||
@@ -19,5 +19,5 @@ form.chat-form(ng-if='user.flags.communityGuidelinesAccepted' ng-submit='postCha
|
||||
.chat-controls.clearfix
|
||||
.chat-buttons
|
||||
input.btn(type='submit', value=env.t('sendChat'), ng-disabled='_sending')
|
||||
button.btn(type="button", ng-click='sync(group)', ng-disabled='_sending')=env.t('toolTipMsg')
|
||||
button.btn(ng-if='group.chat.length > 0 || group.memberCount > 1', type="button", ng-click='sync(group)', ng-disabled='_sending')=env.t('toolTipMsg')
|
||||
include ../../shared/formatting-help
|
||||
|
||||
@@ -16,7 +16,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
|
||||
.panel.panel-default
|
||||
.panel-heading(bindonce='group')
|
||||
h3.panel-title
|
||||
| {{group.name}}
|
||||
span {{group.name}}
|
||||
span.group-leave-join(ng-if='group')
|
||||
a.btn.btn-sm.btn-danger.pull-right(ng-if=":: isMemberOfGroup(User.user._id, group)", ng-click='clickLeave(group, $event)')
|
||||
span.glyphicon.glyphicon-ban-circle
|
||||
@@ -50,16 +50,18 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
|
||||
div(ng-show='!group._editing')
|
||||
img.img-rendering-auto.pull-right(ng-show='group.logo', ng-src='{{group.logo}}')
|
||||
markdown(text='group.description')
|
||||
hr
|
||||
br
|
||||
p=env.t('groupLeader')
|
||||
|:
|
||||
a(class='badge badge-info', ng-click='clickMember(group.leader._id, true)')
|
||||
a.badge.badge-info(ng-click='clickMember(group.leader._id, true)')
|
||||
| {{group.leader.profile.name}}
|
||||
.slight-vertical-padding
|
||||
small.muted=env.t('groupID')
|
||||
|: {{group._id}}
|
||||
|
||||
include ./challenge-box
|
||||
.text-center(ng-if='group.type === "party"')
|
||||
.row.row-margin: .col-sm-6.col-sm-offset-3
|
||||
button.btn.btn-success.btn-block(
|
||||
ng-if='!party.quest.key',
|
||||
ng-click='clickStartQuest();'
|
||||
)=env.t('startAQuest')
|
||||
|
||||
// ------ Members -------
|
||||
.panel.panel-default
|
||||
@@ -67,56 +69,68 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
|
||||
h3.panel-title
|
||||
=env.t('members')
|
||||
button.pull-right.btn.btn-primary(ng-click="openInviteModal(group)")=env.t("inviteFriends")
|
||||
|
||||
.panel-body.modal-fixed-height
|
||||
div.form-group(ng-if='::group.type=="party"')
|
||||
p=env.t('partyList')
|
||||
select.form-control#partyOrder(
|
||||
ng-model='user.party.order',
|
||||
ng-controller='ChatCtrl',
|
||||
ng-options='k as v for (k , v) in partyOrderChoices',
|
||||
ng-change='set({"party.order": user.party.order})'
|
||||
)
|
||||
|
|
||||
select.form-control#partyOrderAscending(
|
||||
ng-model='user.party.orderAscending',
|
||||
ng-controller='ChatCtrl',
|
||||
ng-options='k as v for (k , v) in partyOrderAscendingChoices',
|
||||
ng-change='set({"party.orderAscending": user.party.orderAscending})'
|
||||
)
|
||||
table.table.table-striped(bindonce='group')
|
||||
h4(ng-show='::group.memberCount === 1 && group.type === "party"')=env.t('partyEmpty')
|
||||
table.table.table-striped(ng-show='::group.memberCount > 1 || group.type !== "party"' bindonce='group')
|
||||
tr(ng-repeat='member in group.members track by member._id')
|
||||
td.media
|
||||
// allow leaders to ban members
|
||||
div.pull-left(ng-show='group.leader._id == user.id && member._id != user._id')
|
||||
.pull-left(ng-show='group.leader._id == user.id && member._id != user._id')
|
||||
a.media-object(ng-click='removeMember(group, member, true)')
|
||||
span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip'))
|
||||
a.media-body
|
||||
span(ng-click='clickMember(member._id, true)')
|
||||
| {{member.profile.name}}
|
||||
tr(ng-if='group.memberCount > group.members.length')
|
||||
tr(ng-if='::group.memberCount > group.members.length')
|
||||
td
|
||||
|<span class='badge'>{{group.memberCount - group.members.length}}
|
||||
span.badge {{group.memberCount - group.members.length}}
|
||||
= ' ' + env.t('moreMembers')
|
||||
|</span>
|
||||
|
||||
h4(ng-show='group.invites.length > 0')=env.t('invited')
|
||||
table.table.table-striped
|
||||
tr(ng-repeat='invite in group.invites')
|
||||
td.media
|
||||
// allow leaders to ban members
|
||||
div.pull-left(ng-show='group.leader._id == user.id')
|
||||
.pull-left(ng-show='group.leader._id == user.id')
|
||||
a.media-object(ng-click='removeMember(group, invite, false)')
|
||||
span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip'))
|
||||
a.media-body
|
||||
span(ng-click='clickMember(invite._id, true)')
|
||||
| {{invite.profile.name}}
|
||||
|
||||
.panel.panel-default(ng-if='::group.type=="party" && group.memberCount > 1')
|
||||
.panel-heading
|
||||
h3.panel-title=env.t('partyList')
|
||||
.panel-body
|
||||
.form-group
|
||||
select.form-control#partyOrder(
|
||||
ng-model='user.party.order',
|
||||
ng-controller='ChatCtrl',
|
||||
ng-options='k as v for (k , v) in partyOrderChoices',
|
||||
ng-change='set({"party.order": user.party.order})'
|
||||
)
|
||||
|
|
||||
select.form-control#partyOrderAscending(
|
||||
ng-model='user.party.orderAscending',
|
||||
ng-controller='ChatCtrl',
|
||||
ng-options='k as v for (k , v) in partyOrderAscendingChoices',
|
||||
ng-change='set({"party.orderAscending": user.party.orderAscending})'
|
||||
)
|
||||
|
||||
include ./challenge-box
|
||||
|
||||
div(ng-if="group")
|
||||
a.btn.btn-danger(ng-if=':: isMemberOfGroup(User.user._id, group)', data-id='{{group.id}}', ng-click='clickLeave(group, $event)')=env.t('leave')
|
||||
a.btn.btn-success(ng-if=':: !isMemberOfGroup(User.user._id, group)', ng-click='join(group)')=env.t('join')
|
||||
|
||||
.slight-vertical-padding
|
||||
small.muted=env.t('groupID')
|
||||
| : {{group._id}}
|
||||
.slight-vertical-padding(ng-if='group.type === "party" && group.memberCount === 1')
|
||||
small.muted=env.t('userId')
|
||||
| : {{user._id}}
|
||||
|
||||
.col-md-8
|
||||
div()
|
||||
div
|
||||
textarea.form-control(ng-show='group._editing', rows=6, placeholder=env.t('leaderMsg'), ng-model='group.leaderMessage')
|
||||
table(ng-show='group.leaderMessage')
|
||||
tr
|
||||
@@ -131,3 +145,5 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
|
||||
include ./chat-box
|
||||
|
||||
+chatMessages()
|
||||
h4(ng-if='group.chat.length < 1 && group.type === "party"')=env.t('partyChatEmpty')
|
||||
h4(ng-if='group.chat.length < 1 && group.type === "guild"')=env.t('guildChatEmpty')
|
||||
|
||||
@@ -5,6 +5,7 @@ include ./challenges
|
||||
include ./hall
|
||||
include ./quests/index
|
||||
include ./chat-message
|
||||
include ./party
|
||||
|
||||
script(type='text/ng-template', id='partials/options.social.inbox.html')
|
||||
.options-blurbmenu
|
||||
@@ -26,23 +27,6 @@ script(type='text/ng-template', id='partials/options.social.inbox.html')
|
||||
script(type='text/ng-template', id='partials/options.social.tavern.html')
|
||||
include ./tavern
|
||||
|
||||
script(type='text/ng-template', id='partials/options.social.party.html')
|
||||
div(ng-if='group._id')
|
||||
include ./group
|
||||
div(ng-if='!group._id')
|
||||
div(ng-show='user.invitations.party.id').container-fluid
|
||||
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').container-fluid
|
||||
h2=env.t('createAParty')
|
||||
p
|
||||
=env.t('noPartyText')
|
||||
pre.prettyprint.
|
||||
{{user.id}}
|
||||
p!=env.t('LFG', {linkStart: "<a href='https://habitica.com/#/options/groups/guilds/f2db2a7f-13c5-454d-b3ee-ea1f5089e601'>", linkEnd: "</a>"})
|
||||
include ./create-group
|
||||
|
||||
script(type='text/ng-template', id='partials/options.social.guilds.public.html')
|
||||
.options-blurbmenu
|
||||
small.muted=env.t('blurbGuildsPage')
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
include ../../shared/avatar/generated_avatar
|
||||
|
||||
script(type='text/ng-template', id='partials/options.social.party.html')
|
||||
div(ng-if='group.loadingNewParty')
|
||||
include ./party/loading-new-party
|
||||
|
||||
div(ng-if='group._id')
|
||||
include ./party/leave-party-and-join-another
|
||||
include ./group
|
||||
|
||||
div(ng-if='!group._id && !group.loadingNewParty')
|
||||
include ./party/party-invitation
|
||||
include ./party/start-a-party
|
||||
@@ -0,0 +1,8 @@
|
||||
- var newParty = 'User.user.invitations.party'
|
||||
.containter-fluid(ng-if='#{newParty}.id && party._id')
|
||||
.row.text-center
|
||||
.col-sm-6.col-sm-offset-3.alert.alert-warning
|
||||
p {{::env.t('invitedToNewParty', { partyName: #{newParty}.name })}}
|
||||
p
|
||||
button.btn.btn-success(ng-click='leaveOldPartyAndJoinNewParty(#{newParty}.id, #{newParty}.name)')=env.t('joinNewParty')
|
||||
button.btn.btn-default(ng-click='reject()')=env.t('declineInvitation')
|
||||
@@ -0,0 +1,5 @@
|
||||
.container-fluid
|
||||
.row.text-center: .col-xs-12
|
||||
h2=env.t('loadingNewParty')
|
||||
img(src='/page-loader.gif')
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
.container-fluid(ng-show='user.invitations.party.id')
|
||||
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')
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
.container.text-center(ng-hide='user.invitations.party.id')
|
||||
.row.row-margin: .col-sm-6.col-sm-offset-3
|
||||
a.btn.btn-primary.btn-lg.btn-block(ng-click="inviteOrStartParty(group)")=env.t("startPartyWithFriends")
|
||||
|
||||
.row.row-margin(ng-controller='UserCtrl')
|
||||
span.inline-block.party_preview
|
||||
span.figure.herobox.inline-block
|
||||
.character-sprites(style='margin:0 0 0 4.2em')
|
||||
+generatedAvatar
|
||||
|
||||
.row.row-margin
|
||||
ul
|
||||
li=env.t('partyExplanation1')
|
||||
li=env.t('partyExplanation2')
|
||||
li=env.t('partyExplanation3')
|
||||
|
||||
.row.row-margin
|
||||
h4=env.t('wantToStartParty')
|
||||
|
||||
.row.row-margin
|
||||
.btn.btn-primary(ng-click='inviteOrStartParty(group)')=env.t('inviteFriendsNow')
|
||||
.btn.btn-default(ng-click='create(newGroup); group.loadingNewParty = true')=env.t('inviteFriendsLater')
|
||||
.btn.btn-default(ng-click='openModal("user-id",{track:"Join Existing Party"})')=env.t('joinExistingParty')
|
||||
@@ -2,7 +2,7 @@ mixin participants(questStart)
|
||||
-var isMember = 'group.quest.members[member._id]'
|
||||
table.table.table-striped
|
||||
tr(ng-repeat='member in group.members track by member._id',
|
||||
ng-if='::#{!questStart} || #{isMember}')
|
||||
ng-if='#{!questStart} || #{isMember}')
|
||||
td
|
||||
span(ng-if=':: group.quest.leader && group.quest.leader==member._id') *
|
||||
a: span(ng-click='clickMember(member._id, true)') {{::member.profile.name}}
|
||||
|
||||
@@ -20,5 +20,7 @@ div(ng-if='group.quest.active==true')
|
||||
include ./ianQuestInfo
|
||||
|
||||
unless tavern
|
||||
button.btn.btn-sm.btn-warning(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfRunningQuest(group.quest.leader,group))',
|
||||
button.btn.btn-sm.btn-warning(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfRunningQuest(group.quest.leader,group))',
|
||||
ng-click='questAbort(party)')=env.t('abort')
|
||||
button.btn.btn-sm.btn-warning(ng-if='!(group.quest.leader && group.quest.leader === user._id) && isMemberOfRunningQuest(user._id,group)',
|
||||
ng-click='questLeave(party)')=env.t('leaveQuest')
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
include generated_avatar
|
||||
|
||||
mixin avatar(opts)
|
||||
.character-sprites
|
||||
.addthis_native_toolbox(ng-if='::profile._id==user._id',
|
||||
@@ -16,7 +18,7 @@ mixin avatar(opts)
|
||||
// Show avatar only if not currently affected by visual buff
|
||||
- var buffs = '!profile.stats.buffs'
|
||||
span(ng-if='#{buffs}.snowball && #{buffs}.spookDust && #{buffs}.shinySeed && #{buffs}.seafoam')
|
||||
include generated_avatar
|
||||
+generatedAvatar
|
||||
|
||||
// Mount Head
|
||||
if !opts.minimal
|
||||
|
||||
@@ -4,39 +4,24 @@ mixin costumeSetting(type, options)
|
||||
- var equipped = (options.prefix || '') + 'profile.items.gear.equipped.' + type + (options.suffix || '')
|
||||
span(ng-class="profile.preferences.costume ? #{costume} : #{equipped}")
|
||||
|
||||
// Back accessory
|
||||
+costumeSetting('back')
|
||||
|
||||
span(ng-class="profile.preferences.sleep ? 'skin_' + profile.preferences.skin + '_sleep' : 'skin_' + profile.preferences.skin")
|
||||
|
||||
// Shirt
|
||||
span(class='{{profile.preferences.size}}_shirt_{{profile.preferences.shirt}}')
|
||||
|
||||
// Armor
|
||||
+costumeSetting('armor', {prefix: "profile.preferences.size + '_' + "})
|
||||
|
||||
// Cape collar
|
||||
+costumeSetting('back', {suffix: " + '_collar'"})
|
||||
|
||||
// Body
|
||||
+costumeSetting('body')
|
||||
|
||||
// Empty outline of head
|
||||
span(class='head_0')
|
||||
|
||||
// Hair
|
||||
- var hairTypes = ['base', 'bangs', 'mustache', 'beard']
|
||||
each type in hairTypes
|
||||
span(class='hair_#{type}_{{profile.preferences.hair.#{type}}}_{{profile.preferences.hair.color}}')
|
||||
|
||||
// Head
|
||||
+costumeSetting('eyewear')
|
||||
+costumeSetting('head')
|
||||
+costumeSetting('headAccessory')
|
||||
|
||||
// Flower
|
||||
span(class='hair_flower_{{profile.preferences.hair.flower}}')
|
||||
|
||||
// Items held in hands
|
||||
+costumeSetting('shield')
|
||||
+costumeSetting('weapon')
|
||||
mixin generatedAvatar(options)
|
||||
- options = options || {}
|
||||
+costumeSetting('back')
|
||||
if options.sleep
|
||||
span(ng-class="'skin_' + profile.preferences.skin + '_sleep'")
|
||||
else
|
||||
span(ng-class="profile.preferences.sleep ? 'skin_' + profile.preferences.skin + '_sleep' : 'skin_' + profile.preferences.skin")
|
||||
span(class='{{profile.preferences.size}}_shirt_{{profile.preferences.shirt}}')
|
||||
+costumeSetting('armor', {prefix: "profile.preferences.size + '_' + "})
|
||||
+costumeSetting('back', {suffix: " + '_collar'"})
|
||||
+costumeSetting('body')
|
||||
span.head_0
|
||||
- var hairTypes = ['base', 'bangs', 'mustache', 'beard']
|
||||
each type in hairTypes
|
||||
span(class='hair_#{type}_{{profile.preferences.hair.#{type}}}_{{profile.preferences.hair.color}}')
|
||||
+costumeSetting('eyewear')
|
||||
+costumeSetting('head')
|
||||
+costumeSetting('headAccessory')
|
||||
span(class='hair_flower_{{profile.preferences.hair.flower}}')
|
||||
+costumeSetting('shield')
|
||||
+costumeSetting('weapon')
|
||||
|
||||
@@ -55,6 +55,8 @@ nav.toolbar(ng-controller='MenuCtrl')
|
||||
li
|
||||
a(ui-sref='options.settings.export')=env.t('exportData')
|
||||
ul.toolbar-submenu
|
||||
li
|
||||
a(target='_blank' href='https://habitica.com/static/faq/')=env.t('FAQ')
|
||||
li
|
||||
a(target='_blank' href='http://habitica.wikia.com/wiki/')=env.t('overview')
|
||||
li
|
||||
@@ -65,8 +67,6 @@ nav.toolbar(ng-controller='MenuCtrl')
|
||||
a(target='_blank' href='https://trello.com/c/odmhIqyW/440-read-first-table-of-contents')=env.t('requestAF')
|
||||
li
|
||||
a(target='_blank' href='http://habitica.wikia.com/wiki/Contributing_to_Habitica')=env.t('contributeToHRPG')
|
||||
li
|
||||
a(target='_blank' href='http://habitica.wikia.com/wiki/FAQ')=env.t('FAQ')
|
||||
ul.toolbar-controls
|
||||
li.toolbar-subscribe-button
|
||||
button(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')
|
||||
@@ -147,13 +147,15 @@ nav.toolbar(ng-controller='MenuCtrl')
|
||||
li
|
||||
a(ui-sref='options.settings.export')=env.t('exportData')
|
||||
li.toolbar-button-dropdown.highlight
|
||||
a(target='_blank' href='http://habitica.wikia.com/wiki/')
|
||||
a(target='_blank' href='https://habitica.com/static/faq/')
|
||||
span.glyphicon.glyphicon-question-sign
|
||||
span=env.t('help')
|
||||
a(ng-class='{active: _expandedMenu.menu === "help"}', data-expand-menu, menu='help')
|
||||
span ☰
|
||||
div(ng-show='_expandedMenu.menu === "help"', data-close-menu)
|
||||
ul.toolbar-submenu
|
||||
li
|
||||
a(target='_blank' href='https://habitica.com/static/faq/')=env.t('FAQ')
|
||||
li
|
||||
a(target='_blank' href='http://habitica.wikia.com/wiki/')=env.t('overview')
|
||||
li
|
||||
@@ -164,8 +166,6 @@ nav.toolbar(ng-controller='MenuCtrl')
|
||||
a(target='_blank' href='https://trello.com/c/odmhIqyW/440-read-first-table-of-contents')=env.t('requestAF')
|
||||
li
|
||||
a(target='_blank' href='http://habitica.wikia.com/wiki/Contributing_to_Habitica')=env.t('contributeToHRPG')
|
||||
li
|
||||
a(target='_blank' href='http://habitica.wikia.com/wiki/FAQ')=env.t('FAQ')
|
||||
ul.toolbar-subscribe(ng-if='!user.purchased.plan.customerId')
|
||||
li.toolbar-subscribe-button
|
||||
button.highlight(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')
|
||||
@@ -252,7 +252,7 @@ nav.toolbar(ng-controller='MenuCtrl')
|
||||
a(ui-sref='options.settings.notifications')=env.t('notifications')
|
||||
ul.toolbar-submenu
|
||||
li
|
||||
a(href='http://habitica.wikia.com/wiki/FAQ', target='_blank')=env.t('FAQ')
|
||||
a(href='https://habitica.com/static/faq', target='_blank')=env.t('FAQ')
|
||||
li
|
||||
a(href='https://vimeo.com/57654086', target='_blank')=env.t('tutorials')
|
||||
ul.toolbar-controls
|
||||
@@ -261,7 +261,6 @@ nav.toolbar(ng-controller='MenuCtrl')
|
||||
ul.toolbar-wallet
|
||||
li.toolbar-gems(popover-trigger='mouseenter', popover-title=env.t('gemsPopoverTitle'), popover=env.t('gemsWhatFor'), popover-placement='bottom',popover-append-to-body='true')
|
||||
a.gem-wallet(ng-click='openModal("buyGems",{track:"Gems > Toolbar"})')
|
||||
//-span.task-action-btn.tile.flush.bright.add-gems-btn +
|
||||
span.Pet_Currency_Gem2x.Gems
|
||||
span.gem-text {{user.balance * 4 | number:0}}
|
||||
li.toolbar-currency.gold(popover=env.t('gold') + ' ({{Shared.gold(user.stats.gp)}})', popover-placement='bottom',popover-trigger='mouseenter')
|
||||
|
||||
@@ -16,4 +16,20 @@ mixin previewMarkdown(text)
|
||||
.panel.panel-warning
|
||||
.panel-heading=env.t('msgPreviewHeading')
|
||||
.panel-body.markdown-preview
|
||||
markdown(text='#{text}')
|
||||
markdown(text='#{text}')
|
||||
|
||||
mixin ownedQuests(popoverAppend, popoverPlacement)
|
||||
menu.inventory-list
|
||||
li.customize-menu
|
||||
p.muted(ng-show='questCount < 1')=env.t('noScrolls')
|
||||
div(ng-repeat='type in Content.userCanOwnQuestCategories track by $index')
|
||||
menu.pets-menu(ng-if='Shared.count.questsOfCategory(user.items.quests,type) > 0', label='{{::env.t(type + "Quests")}}')
|
||||
div(ng-repeat='(quest_key,points) in ownedItems(user.items.quests)', ng-init='quest = Content.quests[quest_key]', ng-if='Content.quests[quest_key].category === type')
|
||||
button.customize-option(ng-class='lockQuest(quest) ? "inventory_quest_scroll_locked inventory_quest_scroll_{{::quest.key}}_locked locked" : "inventory_quest_scroll inventory_quest_scroll_{{::quest.key}}"',
|
||||
ng-click='$close(); showQuest(quest_key)',
|
||||
data-popover-html="{{::lockQuest(quest) ? env.t('scrollsPre') : questPopover(quest) | markdown}}",
|
||||
popover-title='{{::quest.text()}}',
|
||||
popover-trigger='mouseenter',
|
||||
popover-append-to-body='#{popoverAppend}',
|
||||
popover-placement='#{popoverPlacement}')
|
||||
.badge.badge-info.stack-count {{points}}
|
||||
|
||||
@@ -15,22 +15,7 @@ script(type='text/ng-template', id='modals/death.html')
|
||||
| {{Math.ceil(user.stats.hp)}} / {{::Shared.maxHealth}}
|
||||
figure.herobox.text-center
|
||||
.character-sprites
|
||||
+costumeSetting('back')
|
||||
span(ng-class="'skin_' + profile.preferences.skin + '_sleep'")
|
||||
span(class='{{profile.preferences.size}}_shirt_{{profile.preferences.shirt}}')
|
||||
+costumeSetting('armor', {prefix: "profile.preferences.size + '_' + "})
|
||||
+costumeSetting('back', {suffix: " + '_collar'"})
|
||||
+costumeSetting('body')
|
||||
span(class='head_0')
|
||||
- var hairTypes = ['base', 'bangs', 'mustache', 'beard']
|
||||
each type in hairTypes
|
||||
span(class='hair_#{type}_{{profile.preferences.hair.#{type}}}_{{profile.preferences.hair.color}}')
|
||||
+costumeSetting('eyewear')
|
||||
+costumeSetting('head')
|
||||
+costumeSetting('headAccessory')
|
||||
span(class='hair_flower_{{profile.preferences.hair.flower}}')
|
||||
+costumeSetting('shield')
|
||||
+costumeSetting('weapon')
|
||||
+generatedAvatar({sleep:true})
|
||||
span(class='knockout')
|
||||
.col-md-6
|
||||
h4(style='margin-top:1.5em')=env.t('dontDespair')
|
||||
|
||||
@@ -13,3 +13,8 @@ include ./rebirth
|
||||
include ./limited
|
||||
include ./invite-friends
|
||||
include ./welcome.jade
|
||||
include ./low-health.jade
|
||||
|
||||
//- Settings
|
||||
script(type='text/ng-template', id='modals/change-day-start.html')
|
||||
include ./settings/change-day-start.jade
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
script(type='text/ng-template', id='modals/invite-friends.html')
|
||||
script(type='text/ng-template', id='modals/invite-guild.html')
|
||||
.modal-header
|
||||
h4=env.t('inviteFriends')
|
||||
.modal-body
|
||||
p.alert.alert-info!=env.t('inviteAlertInfo')
|
||||
|
||||
form.form-inline(ng-submit='invite()')
|
||||
//-.alert.alert-danger(ng-show='_groupError') {{_groupError}}
|
||||
.form-group
|
||||
input.form-control(type='text', placeholder=env.t('userId'), ng-model='invitee')
|
||||
|
|
||||
button.btn.btn-primary(type='submit')=env.t('inviteExistUser')
|
||||
|
||||
form.form-horizontal(ng-submit='inviteNewUsers("uuid")')
|
||||
table.table.table-striped
|
||||
thead
|
||||
tr
|
||||
th=env.t('userId')
|
||||
tbody
|
||||
tr(ng-repeat='user in invitees track by $index')
|
||||
td
|
||||
input.form-control(type='text', ng-model='user.uuid')
|
||||
//- @TODO: Refactor src/controllers/groups api.invite to use promises before implimenting this
|
||||
//- tr
|
||||
//- td
|
||||
//- a.btn.btn-xs.pull-right(ng-click='addUuid()')
|
||||
//- i.glyphicon.glyphicon-plus
|
||||
tr
|
||||
td
|
||||
.col-sm-4.col-sm-offset-8
|
||||
button.btn.btn-primary.btn-block(type='submit')=env.t('sendInvitations')
|
||||
hr
|
||||
|
||||
p.alert.alert-info=env.t('inviteByEmail')
|
||||
|
||||
form.form-horizontal(ng-submit='inviteEmails()')
|
||||
form.form-horizontal(ng-submit='inviteNewUsers("email")')
|
||||
table.table.table-striped
|
||||
thead
|
||||
tr
|
||||
@@ -29,7 +37,7 @@ script(type='text/ng-template', id='modals/invite-friends.html')
|
||||
input.form-control(type='email', ng-model='email.email')
|
||||
tr
|
||||
td(colspan=2)
|
||||
a.btn.btn-xs.pull-right(ng-click='emails = emails.concat([{name:"",email:""}])')
|
||||
a.btn.btn-xs.pull-right(ng-click='addEmail()')
|
||||
i.glyphicon.glyphicon-plus
|
||||
tr
|
||||
td.form-group(colspan=2)
|
||||
@@ -37,12 +45,76 @@ script(type='text/ng-template', id='modals/invite-friends.html')
|
||||
.col-sm-7
|
||||
input.form-control(type='text', ng-model='inviter')
|
||||
.col-sm-4
|
||||
button.btn.btn-primary.btn-wrap(type='submit')=env.t('inviteNewUsers')
|
||||
//-
|
||||
hr
|
||||
p.alert.alert-info=env.t('inviteAlertInfo2')
|
||||
input.form-control(type='text', ng-value='inviteLink({id: party._id, inviter: user._id, name: party.name})')
|
||||
|
||||
button.btn.btn-primary.btn-block(type='submit')=env.t('sendInvitations')
|
||||
.modal-footer
|
||||
button.btn.btn-default(ng-click='$close()')=env.t('close')
|
||||
|
||||
script(type='text/ng-template', id='modals/invite-party.html')
|
||||
.modal-body
|
||||
div(ng-if='!group._id')
|
||||
h4(style='margin-top:0')=env.t('nameYourParty')
|
||||
input.form-control#new-group-name.input-medium.option-content(required, type='text', ng-model='group.name')
|
||||
tabset
|
||||
tab(heading=env.t('inviteByEmail'))
|
||||
p.alert.alert-info=env.t('inviteByEmailExplanation')
|
||||
form.form-horizontal(ng-submit='inviteNewUsers("email")')
|
||||
table.table.table-striped
|
||||
thead
|
||||
tr
|
||||
th=env.t('name')
|
||||
th=env.t('email')
|
||||
tbody
|
||||
tr(ng-repeat='email in emails')
|
||||
td
|
||||
input.form-control(type='text', ng-model='email.name')
|
||||
td
|
||||
input.form-control(type='email', ng-model='email.email')
|
||||
tr
|
||||
td(colspan=2)
|
||||
a.btn.btn-xs.pull-right(ng-click='addEmail()')
|
||||
i.glyphicon.glyphicon-plus
|
||||
tr
|
||||
td.form-group(colspan=2)
|
||||
label.col-sm-1.control-label=env.t('byColon')
|
||||
.col-sm-7
|
||||
input.form-control(type='text', ng-model='inviter')
|
||||
.col-sm-4
|
||||
button.btn.btn-primary.btn-block(type='submit')=env.t('sendInvitations')
|
||||
tab(heading=env.t('inviteExistUser'))
|
||||
p.alert.alert-info!=env.t('inviteAlertInfo')
|
||||
form.form-horizontal(ng-submit='inviteNewUsers("uuid")')
|
||||
table.table.table-striped
|
||||
thead
|
||||
tr
|
||||
th=env.t('userId')
|
||||
tbody
|
||||
tr(ng-repeat='user in invitees track by $index')
|
||||
td
|
||||
input.form-control(type='text', ng-model='user.uuid')
|
||||
//- @TODO: Refactor src/controllers/groups api.invite to use promises before implimenting this
|
||||
//- tr
|
||||
//- td
|
||||
//- a.btn.btn-xs.pull-right(ng-click='addUuid()')
|
||||
//- i.glyphicon.glyphicon-plus
|
||||
tr
|
||||
td
|
||||
.col-sm-4.col-sm-offset-8
|
||||
button.btn.btn-primary.btn-block(type='submit')=env.t('sendInvitations')
|
||||
.row
|
||||
.quest_basilist.pull-left
|
||||
p(style='margin:2em')=env.t('exclusiveQuestScroll')
|
||||
button.btn.btn-default.pull-right(style='margin:auto 1em', ng-click='$close()')=env.t('close')
|
||||
.modal-footer(style='padding-bottom:0',ng-if='!group._id || group.memberCount < 2')
|
||||
p!=env.t('wantExistingParty', {linkStart: "<a href='/#/options/groups/guilds/f2db2a7f-13c5-454d-b3ee-ea1f5089e601'>", linkEnd: "</a>"})
|
||||
pre(style='margin:1em 0 0 0').
|
||||
{{::user._id}}
|
||||
|
||||
script(type='text/ng-template', id='modals/user-id.html')
|
||||
.modal-header
|
||||
h4=env.t('userId')
|
||||
.modal-body
|
||||
p!=env.t('wantExistingParty', {linkStart: "<a href='/#/options/groups/guilds/f2db2a7f-13c5-454d-b3ee-ea1f5089e601'>", linkEnd: "</a>"})
|
||||
pre(style='margin:1em 0 0 0').
|
||||
{{::user._id}}
|
||||
.modal-footer
|
||||
.btn.btn-default.pull-right(ng-click='$close()')=env.t('close')
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
include ../avatar/generated_avatar
|
||||
|
||||
script(type='text/ng-template', id='modals/lowHealth.html')
|
||||
.modal-header
|
||||
h3.text-center=env.t('losingHealthWarning')
|
||||
.modal-body
|
||||
.hero-stats(style='position:absolute; margin-left:9em; width:50%')
|
||||
.meter-label(tooltip=env.t('health'))
|
||||
span.glyphicon.glyphicon-heart
|
||||
.meter.health(tooltip='{{Math.round(user.stats.hp * 100) / 100}}')
|
||||
.bar(ng-style='{"width": Shared.percent(user.stats.hp, Shared.maxHealth)+"%"}')
|
||||
span.meter-text.value
|
||||
| {{Math.ceil(user.stats.hp)}} / {{::Shared.maxHealth}}
|
||||
.herobox.inline-block(style='padding-top:0em; margin-left:16em')
|
||||
.character-sprites(style='display:inline-flex; margin:3em auto')
|
||||
+generatedAvatar
|
||||
p=env.t('losingHealthWarning2')
|
||||
h4=env.t('toRegainHealth')
|
||||
ul
|
||||
li.spaced=env.t('lowHealthTips1')
|
||||
li.spaced=env.t('lowHealthTips2')
|
||||
h4=env.t('losingHealthQuickly')
|
||||
ul
|
||||
li.spaced=env.t('lowHealthTips3')
|
||||
li.spaced=env.t('lowHealthTips4')
|
||||
h4=env.t('goodLuck')
|
||||
.modal-footer(style='margin-top: 0em')
|
||||
a.btn.btn-primary(ng-click='acknowledgeHealthWarning(); $close()')=env.t('ok')
|
||||
@@ -1,24 +1,27 @@
|
||||
include ./quest-rewards
|
||||
include ../mixins
|
||||
|
||||
mixin questInfo
|
||||
.pull-right-sm.text-center
|
||||
.col-centered(class='quest_{{::selectedQuest.key}}')
|
||||
div(ng-if='::selectedQuest.boss')
|
||||
h4 {{::selectedQuest.boss.name()}}
|
||||
p
|
||||
strong=env.t('bossHP') + ': '
|
||||
| {{::selectedQuest.boss.hp}}
|
||||
p
|
||||
strong=env.t('bossStrength') + ': '
|
||||
| {{::selectedQuest.boss.str}}
|
||||
.well.well-sm
|
||||
p
|
||||
strong=env.t('bossHP') + ': '
|
||||
| {{::selectedQuest.boss.hp}}
|
||||
p
|
||||
strong=env.t('bossStrength') + ': '
|
||||
| {{::selectedQuest.boss.str}}
|
||||
div(ng-if='::selectedQuest.collect')
|
||||
p(ng-repeat='(k,v) in ::selectedQuest.collect')
|
||||
strong=env.t('collect') + ': '
|
||||
| {{::selectedQuest.collect[k].count}} {{::selectedQuest.collect[k].text()}}
|
||||
h4=env.t('itemsToCollect')
|
||||
.well.well-sm
|
||||
p(ng-repeat='(k,v) in ::selectedQuest.collect')
|
||||
| {{:: env.t('collectionItems', { number: selectedQuest.collect[k].count, items: selectedQuest.collect[k].text() })}}
|
||||
div(ng-bind-html='::selectedQuest.notes()')
|
||||
quest-rewards(key='{{::selectedQuest.key}}', header=env.t('rewards'))
|
||||
|
||||
script(type='text/ng-template', id='modals/questCompleted.html')
|
||||
script(type='text/ng-template', id='modals/questCompleted.html')
|
||||
.modal-header
|
||||
h4 "{{::Content.quests[user.party.quest.completed].text()}}"
|
||||
=env.t('completed')
|
||||
@@ -29,7 +32,7 @@ script(type='text/ng-template', id='modals/questCompleted.html')
|
||||
.modal-footer
|
||||
button.btn.btn-primary(ng-click='set({"party.quest.completed":""}); $close()')=env.t('ok')
|
||||
|
||||
script(type='text/ng-template', id='modals/showQuest.html')
|
||||
script(type='text/ng-template', id='modals/showQuest.html')
|
||||
.modal-header
|
||||
h4 {{::selectedQuest.text()}}
|
||||
.modal-body
|
||||
@@ -40,9 +43,10 @@ script(type='text/ng-template', id='modals/showQuest.html')
|
||||
p=env.t('questWarning')
|
||||
.modal-footer
|
||||
button.btn.btn-default(ng-click='closeQuest(); $close()')=env.t('cancel')
|
||||
button.btn.btn-primary(ng-click='questInit(); $close()')=env.t('inviteParty')
|
||||
button.btn.btn-primary(ng-click='questInit(); $close()')
|
||||
| {{:: party.memberCount > 1 ? env.t('inviteParty') : env.t('startQuest')}}
|
||||
|
||||
script(type='text/ng-template', id='modals/buyQuest.html')
|
||||
script(type='text/ng-template', id='modals/buyQuest.html')
|
||||
.modal-header
|
||||
h4 {{::selectedQuest.text()}}
|
||||
.modal-body
|
||||
@@ -52,7 +56,7 @@ script(type='text/ng-template', id='modals/buyQuest.html')
|
||||
button.btn.btn-primary(ng-if='::selectedQuest.category !== "gold"', ng-click='purchase("quests", quest); closeQuest(); $close()')=env.t('buyQuest') + ': {{::selectedQuest.value}} ' + env.t('gems')
|
||||
button.btn.btn-primary(ng-if='::selectedQuest.category === "gold"', ng-click='user.ops.buyQuest({params:{key:selectedQuest.key}}); closeQuest(); $close()')=env.t('buyQuest') + ': {{::selectedQuest.goldValue}} ' + env.t('gold')
|
||||
|
||||
script(type='text/ng-template', id='modals/questInvitation.html')
|
||||
script(type='text/ng-template', id='modals/questInvitation.html')
|
||||
.modal-header
|
||||
h4=env.t('questInvitation')
|
||||
| {{::Content.quests[user.party.quest.key].text()}}
|
||||
@@ -62,7 +66,7 @@ script(type='text/ng-template', id='modals/questInvitation.html')
|
||||
div(ng-if='::Content.quests[user.party.quest.key].boss')
|
||||
h4 {{::Content.quests[user.party.quest.key].boss.name()}}
|
||||
p
|
||||
strong=env.t('bossHP') + ': '
|
||||
strong=env.t('bossHP') + ': '
|
||||
| {{::Content.quests[user.party.quest.key].boss.hp}}
|
||||
p
|
||||
strong=env.t('bossStrength') + ': '
|
||||
@@ -70,7 +74,7 @@ script(type='text/ng-template', id='modals/questInvitation.html')
|
||||
div(ng-if='::Content.quests[user.party.quest.key].collect')
|
||||
p(ng-repeat='(k,v) in ::Content.quests[user.party.quest.key].collect')
|
||||
strong=env.t('collect') + ': '
|
||||
| {{::Content.quests[user.party.quest.key].collect[k].count}} {{::Content.quests[user.party.quest.key].collect[k].text()}}
|
||||
| {{::Content.quests[user.party.quest.key].collect[k].count}} {{::Content.quests[user.party.quest.key].collect[k].text()}}
|
||||
div(ng-bind-html='::Content.quests[user.party.quest.key].notes()')
|
||||
quest-rewards(key='{{::user.party.quest.key}}', header=env.t('rewards'))
|
||||
.modal-footer
|
||||
@@ -92,3 +96,13 @@ script(type='text/ng-template', id='modals/questDrop.html')
|
||||
button.btn.btn-default(ng-click='closeQuest(); $close()')=env.t('questLater')
|
||||
button.btn.btn-primary(ng-click='inviteOrStartParty(group); $close()', ng-if='!party.members')=env.t('startAParty')
|
||||
button.btn.btn-primary(ng-click='questInit(); $close()', ng-if='party.members')=env.t('inviteParty')
|
||||
|
||||
script(type='text/ng-template', id='modals/ownedQuests.html')
|
||||
.modal-header
|
||||
h4=env.t('whichQuestStart')
|
||||
.modal-body
|
||||
+ownedQuests(false,'top')
|
||||
.modal-footer
|
||||
h4.pull-left
|
||||
a(ui-sref='options.inventory.quests', ng-click='$close()')=env.t('getMoreQuests')
|
||||
button.btn.btn-default.pull-right(ng-click='$close()')=env.t('neverMind')
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
.text-center
|
||||
.modal-header
|
||||
h3=env.t('changeCustomDayStart')
|
||||
|
||||
.modal-body
|
||||
p!=env.t('nextCron', {time: '<strong class="text-danger">{{::nextCron | date: user.preferences.dateFormat + " @ h:mm a"}}</strong>'})
|
||||
|
||||
.modal-footer
|
||||
p.pull-left-sm=env.t('sureChangeCustomDayStart')
|
||||
a.btn.btn-default(ng-click='$close()')=env.t('close')
|
||||
a.btn.btn-primary(ng-click='saveDayStart(); $close()')=env.t('confirm')
|
||||
+573
-513
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
extends ./layout
|
||||
|
||||
block vars
|
||||
- var layoutEnv = env
|
||||
|
||||
block title
|
||||
title Habitica |
|
||||
=env.t('FAQ')
|
||||
|
||||
block content
|
||||
.row(ng-controller='AccordionCtrl')
|
||||
.col-md-12
|
||||
.page-header
|
||||
h1=env.t('frequentlyAskedQuestions')
|
||||
p.pagemeta
|
||||
=env.t('lastUpdated')
|
||||
|
|
||||
=env.t('September')
|
||||
| 4, 2015
|
||||
|
||||
- var headings = ['overview', 'set-up-tasks', 'sample-tasks', 'task-color', 'health', 'party-with-friends', 'pets-mounts', 'character-classes', 'blue-mana-bar', 'monsters-quests', 'gems', 'bugs-features']
|
||||
- for heading, index in headings
|
||||
a.h2.accordion.collapsed(ng-href='#' + heading, data-toggle='collapse')=env.t('faqQuestion' + index)
|
||||
.clearfix.collapse(id=heading)
|
||||
!=marked(env.t('webFaqAnswer' + index))
|
||||
|
||||
hr
|
||||
|
||||
h3!=marked(env.t('webFaqStillNeedHelp'))
|
||||
@@ -23,7 +23,7 @@ html(ng-app='habitrpg')
|
||||
|
||||
meta(charset='utf-8')
|
||||
meta(name='viewport', content='width=device-width, initial-scale=1.0')
|
||||
|
||||
|
||||
//FIXME for some reason this won't load when in footerCtrl.js#deferredScripts()
|
||||
script(type="text/javascript", src="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5016f6cc44ad68a4", async="async")
|
||||
|
||||
|
||||
@@ -8,5 +8,6 @@ block title
|
||||
title Old News
|
||||
|
||||
block content
|
||||
include ../shared/new-stuff
|
||||
+oldNews()
|
||||
.static-old-news
|
||||
include ../shared/new-stuff
|
||||
+oldNews()
|
||||
|
||||
Reference in New Issue
Block a user