Merge pull request #5855 from HabitRPG/sabrecat/party-page
Party Page Revamp
2
common/dist/sprites/habitrpg-shared.css
vendored
234
common/dist/sprites/spritesmith2.css
vendored
|
|
@ -3012,157 +3012,157 @@
|
|||
}
|
||||
.shop_armor_armoire_gladiatorArmor {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: 0px -1729px;
|
||||
background-position: -123px -1729px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_armoire_goldenToga {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1681px;
|
||||
background-position: -82px -1729px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_armoire_hornedIronArmor {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1558px;
|
||||
background-position: -41px -1729px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_armoire_lunarArmor {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1517px;
|
||||
background-position: -1729px -1640px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_armoire_plagueDoctorOvercoat {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1476px;
|
||||
background-position: -1729px -1599px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_armoire_rancherRobes {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1312px;
|
||||
background-position: -1729px -1558px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_eyewear_armoire_plagueDoctorMask {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1271px;
|
||||
background-position: -1729px -1394px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_armoire_blueHairbow {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1230px;
|
||||
background-position: -1729px -1353px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_armoire_gladiatorHelm {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1189px;
|
||||
background-position: -1729px -1312px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_armoire_goldenLaurels {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1066px;
|
||||
background-position: -1729px -1271px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_armoire_hornedIronHelm {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1025px;
|
||||
background-position: -1729px -1148px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_armoire_lunarCrown {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -984px;
|
||||
background-position: -1729px -1107px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_armoire_plagueDoctorHat {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -943px;
|
||||
background-position: -1729px -1066px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_armoire_rancherHat {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -902px;
|
||||
background-position: -1729px -1025px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_armoire_redFloppyHat {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -861px;
|
||||
background-position: -1729px -984px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_armoire_redHairbow {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -820px;
|
||||
background-position: -1729px -943px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_armoire_royalCrown {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -779px;
|
||||
background-position: -1729px -902px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_armoire_violetFloppyHat {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -738px;
|
||||
background-position: -1729px -861px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_armoire_yellowHairbow {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -697px;
|
||||
background-position: -1729px -820px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_shield_armoire_gladiatorShield {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -41px;
|
||||
background-position: -1729px -779px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_weapon_armoire_basicCrossbow {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px 0px;
|
||||
background-position: -1729px -123px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_weapon_armoire_goldWingStaff {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1696px -1679px;
|
||||
background-position: -1729px -82px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_weapon_armoire_ironCrook {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1655px -1679px;
|
||||
background-position: -1729px -41px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_weapon_armoire_lunarSceptre {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1614px -1679px;
|
||||
background-position: -1729px 0px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_weapon_armoire_mythmakerSword {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1573px -1679px;
|
||||
background-position: -1670px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_weapon_armoire_rancherLasso {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1532px -1679px;
|
||||
background-position: -1629px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
|
@ -3372,205 +3372,205 @@
|
|||
}
|
||||
.shop_armor_healer_1 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1491px -1679px;
|
||||
background-position: -1588px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_healer_2 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1450px -1679px;
|
||||
background-position: -1547px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_healer_3 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1409px -1679px;
|
||||
background-position: -1506px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_healer_4 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1368px -1679px;
|
||||
background-position: -1465px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_healer_5 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1327px -1679px;
|
||||
background-position: -1424px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_rogue_1 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1286px -1679px;
|
||||
background-position: -1383px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_rogue_2 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1163px -1679px;
|
||||
background-position: -1342px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_rogue_3 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1122px -1679px;
|
||||
background-position: -1219px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_rogue_4 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1696px -1638px;
|
||||
background-position: -1178px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_rogue_5 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1655px -1638px;
|
||||
background-position: -1137px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_special_0 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1614px -1638px;
|
||||
background-position: -1670px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_special_1 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1573px -1638px;
|
||||
background-position: -1629px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_special_2 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1532px -1638px;
|
||||
background-position: -1588px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_special_finnedOceanicArmor {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1491px -1638px;
|
||||
background-position: -1547px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_warrior_1 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1450px -1638px;
|
||||
background-position: -1506px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_warrior_2 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1409px -1638px;
|
||||
background-position: -1465px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_warrior_3 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1368px -1638px;
|
||||
background-position: -1424px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_warrior_4 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1327px -1638px;
|
||||
background-position: -1383px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_warrior_5 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1286px -1638px;
|
||||
background-position: -1342px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_wizard_1 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1245px -1638px;
|
||||
background-position: -1301px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_wizard_2 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1204px -1638px;
|
||||
background-position: -1260px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_wizard_3 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1163px -1638px;
|
||||
background-position: -1219px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_wizard_4 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -246px;
|
||||
background-position: -1729px -369px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_wizard_5 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1122px -1638px;
|
||||
background-position: -1178px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.slim_armor_healer_1 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -485px -1638px;
|
||||
background-position: -500px -1638px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_healer_2 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -576px -1638px;
|
||||
background-position: -591px -1638px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_healer_3 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -667px -1638px;
|
||||
background-position: -682px -1638px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_healer_4 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -758px -1638px;
|
||||
background-position: -773px -1638px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_healer_5 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -849px -1638px;
|
||||
background-position: -864px -1638px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_rogue_1 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -940px -1638px;
|
||||
background-position: -955px -1638px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_rogue_2 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -394px -1638px;
|
||||
background-position: -1046px -1638px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_rogue_3 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -303px -1638px;
|
||||
background-position: -409px -1638px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_rogue_4 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -212px -1638px;
|
||||
background-position: -318px -1638px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_rogue_5 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -121px -1638px;
|
||||
background-position: -227px -1638px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
|
|
@ -3660,13 +3660,13 @@
|
|||
}
|
||||
.shop_armor_special_birthday {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1204px -1679px;
|
||||
background-position: -1260px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_special_birthday2015 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1245px -1679px;
|
||||
background-position: -1301px -1679px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
|
@ -3690,313 +3690,319 @@
|
|||
}
|
||||
.broad_armor_special_fallMage {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: 0px -1638px;
|
||||
background-position: -106px -1638px;
|
||||
width: 120px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_fallRogue {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1638px 0px;
|
||||
background-position: 0px -1638px;
|
||||
width: 105px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_fallWarrior {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1547px -1547px;
|
||||
background-position: -1638px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_fallHealer {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1424px -1456px;
|
||||
background-position: -1547px -1547px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_fallMage {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1303px -1456px;
|
||||
background-position: -1394px -1456px;
|
||||
width: 120px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_fallRogue {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1197px -1456px;
|
||||
background-position: -1288px -1456px;
|
||||
width: 105px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_fallWarrior {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1106px -1456px;
|
||||
background-position: -1197px -1456px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_special_fallHealer {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1015px -1456px;
|
||||
background-position: -1106px -1456px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_special_fallRogue {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -909px -1456px;
|
||||
background-position: -1000px -1456px;
|
||||
width: 105px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_special_fallWarrior {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -818px -1456px;
|
||||
background-position: -909px -1456px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_armor_special_fallHealer {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -82px;
|
||||
background-position: -1729px -164px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_special_fallMage {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -123px;
|
||||
background-position: -1729px -205px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_special_fallRogue {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -164px;
|
||||
background-position: -1729px -246px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_armor_special_fallWarrior {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -205px;
|
||||
background-position: -1729px -287px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_fallHealer {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -41px -1729px;
|
||||
background-position: -1729px -328px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_fallMage {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -287px;
|
||||
background-position: -1137px -1638px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_fallRogue {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -328px;
|
||||
background-position: -1729px -410px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_fallWarrior {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -369px;
|
||||
background-position: -1729px -451px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_shield_special_fallHealer {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -410px;
|
||||
background-position: -1729px -492px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_shield_special_fallRogue {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -451px;
|
||||
background-position: -1729px -533px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_shield_special_fallWarrior {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -492px;
|
||||
background-position: -1729px -574px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_weapon_special_fallHealer {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -533px;
|
||||
background-position: -1729px -615px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_weapon_special_fallMage {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -574px;
|
||||
background-position: -1729px -656px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_weapon_special_fallRogue {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -615px;
|
||||
background-position: -1729px -697px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_weapon_special_fallWarrior {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -656px;
|
||||
background-position: -1729px -738px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.slim_armor_special_fallHealer {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -727px -1456px;
|
||||
background-position: -818px -1456px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_fallMage {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -606px -1456px;
|
||||
background-position: -697px -1456px;
|
||||
width: 120px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_fallRogue {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -500px -1456px;
|
||||
background-position: -591px -1456px;
|
||||
width: 105px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_fallWarrior {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -409px -1456px;
|
||||
background-position: -500px -1456px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_special_fallHealer {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -318px -1456px;
|
||||
background-position: -409px -1456px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_special_fallMage {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -197px -1456px;
|
||||
background-position: -288px -1456px;
|
||||
width: 120px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_special_fallRogue {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -91px -1456px;
|
||||
background-position: -182px -1456px;
|
||||
width: 105px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_special_fallWarrior {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: 0px -1456px;
|
||||
background-position: -91px -1456px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_gaymerx {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1456px -1365px;
|
||||
background-position: 0px -1456px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_gaymerx {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1456px -1274px;
|
||||
background-position: -1456px -1365px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_armor_special_gaymerx {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1107px;
|
||||
background-position: -1729px -1189px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_gaymerx {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1148px;
|
||||
background-position: -1729px -1230px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.slim_armor_special_gaymerx {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1456px -1183px;
|
||||
background-position: -1456px -1274px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.back_mystery_201402 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1456px -1092px;
|
||||
background-position: -1456px -1183px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_mystery_201402 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1456px -1001px;
|
||||
background-position: -1456px -1092px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_mystery_201402 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1456px -910px;
|
||||
background-position: -1456px -1001px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_armor_mystery_201402 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1353px;
|
||||
background-position: -1729px -1435px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_back_mystery_201402 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1394px;
|
||||
background-position: -1729px -1476px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_mystery_201402 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1435px;
|
||||
background-position: -1729px -1517px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.slim_armor_mystery_201402 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1456px -819px;
|
||||
background-position: -1456px -910px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_mystery_201403 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1456px -728px;
|
||||
background-position: -1456px -819px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_mystery_201403 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1456px -637px;
|
||||
background-position: -1456px -728px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_armor_mystery_201403 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1599px;
|
||||
background-position: -1729px -1681px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_headAccessory_mystery_201403 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1744px -1640px;
|
||||
background-position: 0px -1729px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.slim_armor_mystery_201403 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1456px -546px;
|
||||
background-position: -1456px -637px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.back_mystery_201404 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1456px -455px;
|
||||
background-position: -1456px -546px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_mystery_201404 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -1031px -1638px;
|
||||
background-position: -1456px -455px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_back_mystery_201404 {
|
||||
background-image: url(spritesmith2.png);
|
||||
background-position: -164px -1729px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
|
|
|||
BIN
common/dist/sprites/spritesmith2.png
vendored
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
784
common/dist/sprites/spritesmith3.css
vendored
BIN
common/dist/sprites/spritesmith3.png
vendored
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 92 KiB |
858
common/dist/sprites/spritesmith4.css
vendored
BIN
common/dist/sprites/spritesmith4.png
vendored
|
Before Width: | Height: | Size: 598 KiB After Width: | Height: | Size: 608 KiB |
BIN
common/img/sprites/spritesmith/misc/party_preview.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
|
@ -14,7 +14,7 @@
|
|||
"resources": "Resources",
|
||||
"tavernTalk": "Tavern Talk",
|
||||
"tavernAlert1": "Note: if you're reporting a bug, the developers won't see it here. Please",
|
||||
"tavernAlert2": "use Github instead",
|
||||
"tavernAlert2": "use GitHub instead",
|
||||
"moderatorIntro1": "Tavern and guild moderators are: ",
|
||||
"communityGuidelines": "Community Guidelines",
|
||||
"communityGuidelinesRead1": "Please read our",
|
||||
|
|
@ -24,11 +24,17 @@
|
|||
"updatedParty": "Party settings updated.",
|
||||
"noPartyText": "You are either not in a party or your party is taking a while to load. You can either create one and invite friends, or if you want to join an existing party, have them enter your Unique User ID below and then come back here to look for the invitation:",
|
||||
"LFG": "To advertise your new party or find one to join, go to the <%= linkStart %>Party Wanted (Looking for Group)<%= linkEnd %> Guild.",
|
||||
"wantExistingParty": "Want to join an existing party? Go to the <%= linkStart %>Party Wanted Guild<%= linkEnd %> and post this User ID:",
|
||||
"joinExistingParty": "Join Someone Else's Party",
|
||||
"create": "Create",
|
||||
"userId": "User ID",
|
||||
"invite": "Invite",
|
||||
"leave": "Leave",
|
||||
"invitedTo": "Invited to <%= name %>",
|
||||
"invitedToNewParty": "You were invited to join a party! Do you want to leave this party and join <%= partyName %>?",
|
||||
"joinNewParty": "Join New Party",
|
||||
"declineInvitation": "Decline Invitation",
|
||||
"loadingNewParty": "Your new party is loading. Please wait...",
|
||||
"newMsg": "New message in \"<%= name %>\"",
|
||||
"chat": "Chat",
|
||||
"sendChat": "Send Chat",
|
||||
|
|
@ -44,8 +50,8 @@
|
|||
"groupDescr": "Description shown in public Guilds list (Markdown OK)",
|
||||
"logoUrl": "Logo URL",
|
||||
"assignLeader": "Assign Group Leader",
|
||||
"members": "Member(s)",
|
||||
"partyList": "Order for party members in header (reload browser after changing order)",
|
||||
"members": "Members",
|
||||
"partyList": "Order for party members in header",
|
||||
"banTip": "Boot Member",
|
||||
"moreMembers": "more members",
|
||||
"invited": "Invited",
|
||||
|
|
@ -106,11 +112,16 @@
|
|||
"leaderOnlyChallenges": "Only group leader can create challenges",
|
||||
"sendGift": "Send Gift",
|
||||
"inviteFriends": "Invite Friends",
|
||||
"inviteAlertInfo": "Invite friends by <a href='http://habitica.wikia.com/wiki/API_Options' target='_blank'>User ID</a> here.",
|
||||
"inviteExistUser": "Invite Existing User",
|
||||
"inviteByEmail": "Invite friends by email. If they join via your email, they'll automatically be invited to this group.",
|
||||
"inviteByEmail": "Invite by Email",
|
||||
"inviteByEmailExplanation": "If a friend joins Habitica via your email, they'll automatically be invited to your party!",
|
||||
"inviteFriendsNow": "Invite Friends Now",
|
||||
"inviteFriendsLater": "Invite Friends Later",
|
||||
"inviteAlertInfo": "If you have friends already using Habitica, invite them by <a href='http://habitica.wikia.com/wiki/API_Options' target='_blank'>User ID</a> here.",
|
||||
"inviteExistUser": "Invite Existing Users",
|
||||
"byColon": "By:",
|
||||
"inviteNewUsers": "Invite New User(s)",
|
||||
"inviteNewUsers": "Invite New Users",
|
||||
"sendInvitations": "Send Invitations",
|
||||
"invitationsSent": "Invitations sent!",
|
||||
"inviteAlertInfo2": "Or share this link (copy/paste):",
|
||||
"sendGiftHeading": "Send Gift to <%= name %>",
|
||||
"sendGiftGemsBalance": "From <%= number %> Gems",
|
||||
|
|
@ -120,7 +131,19 @@
|
|||
"sendGiftMessagePlaceholder": "Personal message (optional)",
|
||||
"sendGiftSubscription": "<%= months %> Month(s): $<%= price %>",
|
||||
"battleWithFriends": "Battle Monsters With Friends",
|
||||
"startPartyWithFriends": "Start a Party with your friends!",
|
||||
"startAParty": "Start a Party",
|
||||
"addToParty": "Add someone to your party",
|
||||
"likePost": "Click if you like this post!"
|
||||
"likePost": "Click if you like this post!",
|
||||
"partyExplanation1": "Play Habitica with friends to stay accountable!",
|
||||
"partyExplanation2": "Battle monsters and create Challenges!",
|
||||
"partyExplanation3": "Invite friends now to earn a Quest Scroll!",
|
||||
"wantToStartParty": "Do you want to start a party?",
|
||||
"exclusiveQuestScroll": "Inviting a friend to your party will grant you an exclusive Quest Scroll to battle the Basi-List together!",
|
||||
"nameYourParty": "Name your new party!",
|
||||
"partyEmpty": "You're the only one in your party. Invite your friends!",
|
||||
"partyChatEmpty": "Your party chat is empty! Type a message in the box above to start chatting.",
|
||||
"guildChatEmpty": "This guild's chat is empty! Type a message in the box above to start chatting.",
|
||||
"possessiveParty": "<%= name %>'s Party"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"youReceived": "You've Received",
|
||||
"dropQuestCongrats": "Congratulations on earning this quest scroll! You can invite your party to begin the quest now, or come back to it any time in your Inventory > Quests.",
|
||||
"questSend": "Clicking \"Invite\" will send an invitation to your party members. When all members have accepted or denied, the quest begins. See status under Social > Party.",
|
||||
"inviteParty": "Invite Party",
|
||||
"inviteParty": "Invite Party to Quest",
|
||||
"questInvitation": "Quest Invitation: ",
|
||||
"questInvitationTitle": "Quest Invitation",
|
||||
"questInvitationInfo": "Invitation for the Quest <%= quest %>",
|
||||
|
|
@ -29,12 +29,16 @@
|
|||
"bossStrength": "Boss Strength",
|
||||
"collect": "Collect",
|
||||
"collected": "Collected",
|
||||
"collectionItems": "<%= number %> <%= items %>",
|
||||
"itemsToCollect": "Items to Collect",
|
||||
"bossDmg1": "Each completed Daily and To-Do and each positive Habit hurts the boss. Hurt it more with redder tasks or Brutal Smash and Burst of Flames. The boss will deal damage to every quest participant for every Daily you've missed (multiplied by the boss's Strength) in addition to your regular damage, so keep your party healthy by completing your Dailies! <strong>All damage to and from a boss is tallied on cron (your day roll-over).</strong>",
|
||||
"bossDmg2": "Only participants will fight the boss and share in the quest loot.",
|
||||
"tavernBossInfo": "To hurt a world boss, complete your Dailies and To-Dos. Higher task damage means higher boss damage (completing reds, Mage spells, Warrior attacks, etc). For each Daily you've missed (multiplied by the boss's Strength), the boss's Rage will increase. Once his Rage reaches max, something bad will happen - so complete your Dailies! <strong>All damage to and from a boss is tallied on cron (your day roll-over).</strong>",
|
||||
"bossColl1": "To collect items, do your positive tasks. Quest items drop just like normal items; however, you won't see the drops until the next day, then everything you've found will be tallied up and contributed to the pile.",
|
||||
"bossColl2": "Only participants can collect items and share in the quest loot.",
|
||||
"abort": "Abort",
|
||||
"leaveQuest": "Leave Quest",
|
||||
"sureLeave": "Are you sure you want to leave the active quest? All your quest progress will be lost.",
|
||||
"questOwner": "Quest Owner",
|
||||
"questOwnerNotInPendingQuest": "The quest owner has left the quest and can no longer begin it. It is recommended that you cancel it now. The quest owner will retain possession of the quest scroll.",
|
||||
"questOwnerNotInRunningQuest": "The quest owner has left the quest. You can abort the quest if you need to. You can also allow it to keep running and all remaining participants will receive the quest rewards when the quest finishes.",
|
||||
|
|
@ -59,5 +63,9 @@
|
|||
"doubleSureAbort": "Are you double sure? Make sure they won't hate you forever!",
|
||||
"questWarning": "If new players join the party before the quest starts, they will also receive an invitation. However once the quest has started, no new party members can join the quest.",
|
||||
"bossRageTitle": "Rage",
|
||||
"bossRageDescription": "When this bar fills, the boss will unleash a special attack!"
|
||||
"bossRageDescription": "When this bar fills, the boss will unleash a special attack!",
|
||||
"startAQuest": "START A QUEST",
|
||||
"startQuest": "Start Quest",
|
||||
"whichQuestStart": "Which quest do you want to start?",
|
||||
"getMoreQuests": "Get more quests"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1203,6 +1203,12 @@ api.food =
|
|||
_.each api.food, (food,key) ->
|
||||
_.defaults food, {value: 1, key, notes: t('foodNotes')}
|
||||
|
||||
api.userCanOwnQuestCategories = [
|
||||
'unlockable'
|
||||
'gold'
|
||||
'pet'
|
||||
]
|
||||
|
||||
api.quests =
|
||||
|
||||
dilatory:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ function dropPetsCurrentlyOwned(pets) {
|
|||
return count;
|
||||
}
|
||||
|
||||
|
||||
function mountMasterProgress(mounts) {
|
||||
var count = 0;
|
||||
_(DROP_ANIMALS).each(function(animal) {
|
||||
|
|
@ -51,9 +50,23 @@ function remainingGearInSet(userGear, set) {
|
|||
return count;
|
||||
}
|
||||
|
||||
function questsOfCategory(userQuests, category) {
|
||||
var quests = _.filter(content.quests, function(quest) {
|
||||
var categoryMatches = quest.category === category;
|
||||
var hasQuest = userQuests[quest.key];
|
||||
|
||||
return categoryMatches && hasQuest;
|
||||
});
|
||||
|
||||
var count = _.size(quests);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
beastMasterProgress: beastMasterProgress,
|
||||
dropPetsCurrentlyOwned: dropPetsCurrentlyOwned,
|
||||
mountMasterProgress: mountMasterProgress,
|
||||
remainingGearInSet: remainingGearInSet
|
||||
remainingGearInSet: remainingGearInSet,
|
||||
questsOfCategory: questsOfCategory
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ module.exports = function(config) {
|
|||
'test/spec/mocks/**/*.js',
|
||||
|
||||
"website/public/js/env.js",
|
||||
|
||||
"website/public/js/app.js",
|
||||
"common/script/public/config.js",
|
||||
"common/script/public/userServices.js",
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ describe "Party", ->
|
|||
group = undefined
|
||||
participating = []
|
||||
notParticipating = []
|
||||
before (done) ->
|
||||
beforeEach (done) ->
|
||||
# Tavern boss, side-by-side
|
||||
Group.update(
|
||||
_id: "habitrpg"
|
||||
|
|
@ -310,6 +310,20 @@ describe "Party", ->
|
|||
expect(_.size(res.body.quest.members)).to.equal 3
|
||||
done()
|
||||
|
||||
it "allows quest participants to leave quest", (done) ->
|
||||
leavingMember = party[1]
|
||||
expect(group.quest.members[leavingMember._id]).to.eql(true)
|
||||
|
||||
request.post(baseURL + "/groups/" + group._id + "/questLeave")
|
||||
.set("X-API-User", leavingMember._id)
|
||||
.set("X-API-Key", leavingMember.apiToken)
|
||||
.end (err, res) ->
|
||||
expectCode res, 204
|
||||
request.get(baseURL + '/groups/party')
|
||||
.end (err, res) ->
|
||||
expect(res.body.quest.members[leavingMember._id]).to.not.be.ok
|
||||
done()
|
||||
|
||||
xit "Hurts the boss", (done) ->
|
||||
request.post(baseURL + "/user/batch-update").end (res) ->
|
||||
user = res.body
|
||||
|
|
|
|||
|
|
@ -154,4 +154,23 @@ describe('count', function() {
|
|||
expect(dropPets).to.eql(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('questsOfCategory', function() {
|
||||
|
||||
it('counts user quest scrolls of a particular category', function() {
|
||||
var quests = {
|
||||
"atom1": 2,
|
||||
"whale": 4,
|
||||
"kraken": 2,
|
||||
"sheep": 1,
|
||||
"goldenknight2": 1
|
||||
};
|
||||
var petQuestCount = count.questsOfCategory(quests, 'pet');
|
||||
var unlockableQuestCount = count.questsOfCategory(quests, 'unlockable');
|
||||
var goldQuestCount = count.questsOfCategory(quests, 'gold');
|
||||
expect(petQuestCount).to.eql(3);
|
||||
expect(unlockableQuestCount).to.eql(2);
|
||||
expect(goldQuestCount).to.eql(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
382
test/server_side/controllers/groups.test.js
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
var sinon = require('sinon');
|
||||
var chai = require("chai");
|
||||
chai.use(require("sinon-chai"));
|
||||
var expect = chai.expect;
|
||||
|
||||
var Q = require('q');
|
||||
var Group = require('../../../website/src/models/group').model;
|
||||
var groupsController = require('../../../website/src/controllers/groups');
|
||||
|
||||
describe('Groups Controller', function() {
|
||||
var utils = require('../../../website/src/utils');
|
||||
|
||||
describe('#invite', function() {
|
||||
var res, req, user, group;
|
||||
|
||||
beforeEach(function() {
|
||||
group = {
|
||||
_id: 'group-id',
|
||||
name: 'group-name',
|
||||
type: 'party',
|
||||
members: [
|
||||
'user-id',
|
||||
'another-user'
|
||||
],
|
||||
save: sinon.stub().yields(),
|
||||
markModified: sinon.spy()
|
||||
};
|
||||
|
||||
user = {
|
||||
_id: 'user-id',
|
||||
name: 'inviter',
|
||||
email: 'inviter@example.com',
|
||||
save: sinon.stub().yields(),
|
||||
markModified: sinon.spy()
|
||||
};
|
||||
|
||||
res = {
|
||||
locals: {
|
||||
group: group,
|
||||
user: user
|
||||
},
|
||||
json: sinon.stub(),
|
||||
send: sinon.stub()
|
||||
};
|
||||
|
||||
req = {
|
||||
body: {}
|
||||
};
|
||||
});
|
||||
|
||||
context('uuids', function() {
|
||||
beforeEach(function() {
|
||||
req.body.uuids = ['invited-user'];
|
||||
});
|
||||
|
||||
it('returns 400 if user not found');
|
||||
|
||||
it('returns a 400 if user is already in the group');
|
||||
|
||||
it('retuns 400 if user was already invited to that group');
|
||||
|
||||
it('returns 400 if user is already pending an invitation');
|
||||
|
||||
it('returns 400 is user is already in another party');
|
||||
|
||||
it('emails invited user');
|
||||
|
||||
it('does not email invited user if email preference is set to false');
|
||||
});
|
||||
|
||||
context('emails', function() {
|
||||
var EmailUnsubscription = require('../../../website/src/models/emailUnsubscription').model;
|
||||
var execStub, selectStub;
|
||||
|
||||
beforeEach(function() {
|
||||
sinon.stub(utils, 'encrypt').returns('http://link.com');
|
||||
sinon.stub(utils, 'getUserInfo').returns({
|
||||
name: user.name,
|
||||
email: user.email
|
||||
});
|
||||
execStub = sinon.stub();
|
||||
selectStub = sinon.stub().returns({
|
||||
exec: execStub
|
||||
});
|
||||
sinon.stub(User, 'findOne').returns({
|
||||
select: selectStub
|
||||
});
|
||||
sinon.stub(EmailUnsubscription, 'findOne');
|
||||
sinon.stub(utils, 'txnEmail');
|
||||
|
||||
req.body.emails = [{email: 'user@example.com', name: 'user'}];
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
User.findOne.restore();
|
||||
EmailUnsubscription.findOne.restore();
|
||||
utils.encrypt.restore();
|
||||
utils.getUserInfo.restore();
|
||||
utils.txnEmail.restore();
|
||||
});
|
||||
|
||||
it('emails user with invite', function() {
|
||||
execStub.yields(null, null);
|
||||
EmailUnsubscription.findOne.yields(null, null);
|
||||
|
||||
groupsController.invite(req, res);
|
||||
|
||||
expect(utils.txnEmail).to.be.calledOnce;
|
||||
expect(utils.txnEmail).to.be.calledWith(
|
||||
{ email: 'user@example.com', name: 'user' },
|
||||
'invite-friend',
|
||||
[
|
||||
{ name: 'LINK', content: '?partyInvite=http://link.com' },
|
||||
{ name: 'INVITER', content: 'inviter' },
|
||||
{ name: 'REPLY_TO_ADDRESS', content: 'inviter@example.com' }
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('does not email user if user is on unsubscribe list', function() {
|
||||
EmailUnsubscription.findOne.yields(null, {_id: 'on-list'});
|
||||
|
||||
expect(utils.txnEmail).to.not.be.called;
|
||||
});
|
||||
|
||||
it('checks if a user with provided email already exists');
|
||||
});
|
||||
|
||||
context('others', function() {
|
||||
it ('returns a 400 error', function() {
|
||||
groupsController.invite(req, res);
|
||||
|
||||
expect(res.json).to.be.calledOnce;
|
||||
expect(res.json).to.be.calledWith(
|
||||
400,
|
||||
{ err: 'Can invite only by email or uuid' }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#leave', function() {
|
||||
var res, req, user, group;
|
||||
|
||||
beforeEach(function() {
|
||||
group = {
|
||||
_id: 'group-id',
|
||||
type: 'party',
|
||||
members: [
|
||||
'user-id',
|
||||
'another-user'
|
||||
],
|
||||
save: sinon.stub().yields(),
|
||||
markModified: sinon.spy()
|
||||
};
|
||||
|
||||
user = {
|
||||
_id: 'user-id',
|
||||
save: sinon.stub().yields(),
|
||||
markModified: sinon.spy()
|
||||
};
|
||||
|
||||
res = {
|
||||
locals: {
|
||||
group: group,
|
||||
user: user
|
||||
},
|
||||
json: sinon.stub(),
|
||||
send: sinon.stub()
|
||||
};
|
||||
|
||||
req = {
|
||||
query: { keep: 'keep' }
|
||||
};
|
||||
});
|
||||
|
||||
context('party', function() {
|
||||
beforeEach(function() {
|
||||
group.type = 'party';
|
||||
group.quest = {
|
||||
leader : 'another-user',
|
||||
active: true,
|
||||
members: {
|
||||
'user-id': true,
|
||||
'another-user': true
|
||||
},
|
||||
key : 'vice1',
|
||||
progress : {
|
||||
hp : 364,
|
||||
collect : {}
|
||||
}
|
||||
};
|
||||
|
||||
sinon.spy(Group, 'update');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
Group.update.restore();
|
||||
});
|
||||
|
||||
it('prevents user from leaving party if quest is active', function() {
|
||||
user.party = {
|
||||
quest : {
|
||||
key : 'vice1',
|
||||
progress : {
|
||||
up : 50,
|
||||
down : 0,
|
||||
collect : {}
|
||||
},
|
||||
completed : null,
|
||||
RSVPNeeded : false
|
||||
}
|
||||
}
|
||||
|
||||
groupsController.leave(req, res);
|
||||
|
||||
expect(Group.update).to.not.be.called;
|
||||
expect(res.json).to.be.calledOnce;
|
||||
expect(res.json).to.be.calledWith(403, 'You cannot leave party during an active quest. Please leave the quest first');
|
||||
});
|
||||
|
||||
it('leaves party if quest is not active', function() {
|
||||
user.party = { quest: { key: null } };
|
||||
|
||||
groupsController.leave(req, res);
|
||||
|
||||
expect(Group.update).to.be.calledOnce;
|
||||
expect(res.json).to.not.be.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#questLeave', function() {
|
||||
var res, req, group, user, saveSpy;
|
||||
|
||||
beforeEach(function() {
|
||||
sinon.stub(Q, 'all').returns({
|
||||
done: sinon.stub().yields()
|
||||
});
|
||||
group = {
|
||||
_id: 'group-id',
|
||||
type: 'party',
|
||||
quest: {
|
||||
leader : 'another-user',
|
||||
active: true,
|
||||
members: {
|
||||
'user-id': true,
|
||||
'another-user': true
|
||||
},
|
||||
key : 'vice1',
|
||||
progress : {
|
||||
hp : 364,
|
||||
collect : {}
|
||||
}
|
||||
},
|
||||
save: sinon.stub().yields(),
|
||||
markModified: sinon.spy()
|
||||
};
|
||||
|
||||
user = {
|
||||
_id: 'user-id',
|
||||
party : {
|
||||
quest : {
|
||||
key : 'vice1',
|
||||
progress : {
|
||||
up : 50,
|
||||
down : 0,
|
||||
collect : {}
|
||||
},
|
||||
completed : null,
|
||||
RSVPNeeded : false
|
||||
}
|
||||
},
|
||||
save: sinon.stub().yields(),
|
||||
markModified: sinon.spy()
|
||||
};
|
||||
|
||||
res = {
|
||||
locals: {
|
||||
group: group,
|
||||
user: user
|
||||
},
|
||||
json: sinon.stub(),
|
||||
send: sinon.stub()
|
||||
};
|
||||
|
||||
req = { };
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
Q.all.restore();
|
||||
});
|
||||
|
||||
context('error conditions', function() {
|
||||
it('errors if quest is not active', function() {
|
||||
group.quest.active = false;
|
||||
|
||||
groupsController.questLeave(req, res);
|
||||
|
||||
expect(res.json).to.be.calledOnce;
|
||||
expect(res.json).to.be.calledWith(
|
||||
404,
|
||||
{ err: 'No active quest to leave' }
|
||||
);
|
||||
});
|
||||
|
||||
it('errors if user is not part of quest', function() {
|
||||
delete group.quest.members[user._id];
|
||||
|
||||
groupsController.questLeave(req, res);
|
||||
|
||||
expect(res.json).to.be.calledOnce;
|
||||
expect(res.json).to.be.calledWith(
|
||||
403,
|
||||
{ err: 'You are not part of the quest' }
|
||||
);
|
||||
});
|
||||
|
||||
it('does not allow quest leader to leave quest', function() {
|
||||
group.quest.leader = 'user-id';
|
||||
|
||||
groupsController.questLeave(req, res);
|
||||
|
||||
expect(res.json).to.be.calledOnce;
|
||||
expect(res.json).to.be.calledWith(
|
||||
403,
|
||||
{ err: 'Quest leader cannot leave quest' }
|
||||
);
|
||||
});
|
||||
|
||||
it('sends 500 if group cannot save', function() {
|
||||
Q.all.returns({
|
||||
done: sinon.stub().callsArgWith(1, {err: 'save error'})
|
||||
});
|
||||
var nextSpy = sinon.spy();
|
||||
|
||||
groupsController.questLeave(req, res, nextSpy);
|
||||
|
||||
expect(res.json).to.not.be.called;
|
||||
expect(nextSpy).to.be.calledOnce;
|
||||
expect(nextSpy).to.be.calledWith({err: 'save error'});
|
||||
});
|
||||
});
|
||||
|
||||
context('success', function() {
|
||||
it('removes user from quest', function() {
|
||||
expect(group.quest.members[user._id]).to.exist;
|
||||
|
||||
groupsController.questLeave(req, res);
|
||||
|
||||
expect(group.quest.members[user._id]).to.not.exist;
|
||||
});
|
||||
|
||||
it('scrubs quest data from user', function() {
|
||||
user.party.quest.progress = {
|
||||
up: 100,
|
||||
down: 32,
|
||||
collect: {
|
||||
foo: 12,
|
||||
bar: 4
|
||||
}
|
||||
};
|
||||
|
||||
groupsController.questLeave(req, res);
|
||||
|
||||
expect(user.party.quest.key).to.not.exist;
|
||||
expect(user.party.quest.progress).to.eql({
|
||||
up: 0,
|
||||
down: 0,
|
||||
collect: {}
|
||||
});
|
||||
});
|
||||
|
||||
it('sends back 204 on success', function() {
|
||||
groupsController.questLeave(req, res);
|
||||
|
||||
expect(res.send).to.be.calledOnce;
|
||||
expect(res.send).to.be.calledWith(204);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
194
test/spec/controllers/inviteToGroupCtrlSpec.js
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
'use strict';
|
||||
|
||||
describe('Invite to Group Controller', function() {
|
||||
var scope, ctrl, groups, user, guild, $rootScope;
|
||||
|
||||
beforeEach(function() {
|
||||
user = specHelper.newUser({
|
||||
profile: { name: 'Mario' }
|
||||
});
|
||||
|
||||
module(function($provide) {
|
||||
$provide.value('User', {});
|
||||
$provide.value('injectedGroup', { user: user });
|
||||
});
|
||||
|
||||
inject(function($rootScope, $controller, Groups){
|
||||
scope = $rootScope.$new();
|
||||
|
||||
// Load RootCtrl to ensure shared behaviors are loaded
|
||||
$controller('RootCtrl', {$scope: scope, User: {user: user}});
|
||||
|
||||
ctrl = $controller('InviteToGroupCtrl', {$scope: scope, User: {user: user}});
|
||||
|
||||
groups = Groups;
|
||||
});
|
||||
});
|
||||
|
||||
describe('addEmail', function() {
|
||||
it('adds blank email to email list', function() {
|
||||
scope.emails = [{name: 'Mario', email: 'mario@mushroomkingdom.com'}];
|
||||
scope.addEmail();
|
||||
|
||||
expect(scope.emails).to.eql([{name: 'Mario', email: 'mario@mushroomkingdom.com'}, {name: '', email: ''}]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addUuid', function() {
|
||||
it('adds blank uuid to invitees list', function() {
|
||||
scope.invitees = [{uuid: 'user1'}];
|
||||
scope.addUuid();
|
||||
|
||||
expect(scope.invitees).to.eql([{uuid: 'user1'}, {uuid: ''}]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('inviteNewUsers', function() {
|
||||
beforeEach(function() {
|
||||
scope.group = specHelper.newGroup({
|
||||
type: 'party',
|
||||
$save: sinon.stub().returns({
|
||||
then: function(cb) { cb(); }
|
||||
})
|
||||
});
|
||||
|
||||
sandbox.stub(groups.Group, 'invite');
|
||||
});
|
||||
|
||||
context('if the party does not already exist', function() {
|
||||
beforeEach(function() {
|
||||
delete scope.group._id;
|
||||
});
|
||||
|
||||
it('saves the group if a new group is being created', function() {
|
||||
scope.inviteNewUsers('uuid');
|
||||
expect(scope.group.$save).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('uses provided name', function() {
|
||||
scope.group.name = 'test party';
|
||||
scope.inviteNewUsers('uuid');
|
||||
expect(group.name).to.eql('test party');
|
||||
});
|
||||
|
||||
it('names the group if no name is provided', function() {
|
||||
scope.group.name = '';
|
||||
scope.inviteNewUsers('uuid');
|
||||
expect(group.name).to.eql(env.t('possessiveParty', {name: user.profile.name}));
|
||||
});
|
||||
});
|
||||
|
||||
context('email', function() {
|
||||
it('invites user with emails', function() {
|
||||
scope.emails = [
|
||||
{name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'},
|
||||
{name: 'Mario', email: 'mario@tmk.com'}
|
||||
];
|
||||
|
||||
scope.inviteNewUsers('email');
|
||||
expect(groups.Group.invite).to.be.calledOnce;
|
||||
expect(groups.Group.invite).to.be.calledWith({
|
||||
gid: scope.group._id,
|
||||
}, {
|
||||
inviter: user.profile.name,
|
||||
emails: [
|
||||
{name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'},
|
||||
{name: 'Mario', email: 'mario@tmk.com'}
|
||||
]
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('resets email list after sending', function() {
|
||||
groups.Group.invite.yields();
|
||||
scope.emails[0].name = 'Luigi';
|
||||
scope.emails[0].email = 'mario_bro@themushroomkingdom.com';
|
||||
|
||||
scope.inviteNewUsers('email');
|
||||
|
||||
expect(scope.emails).to.eql([{name:'', email: ''},{name:'', email: ''}]);
|
||||
});
|
||||
|
||||
it('filters out blank email inputs', function() {
|
||||
scope.emails = [
|
||||
{name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'},
|
||||
{name: 'Toad', email: ''},
|
||||
{name: 'Mario', email: 'mario@tmk.com'}
|
||||
];
|
||||
|
||||
scope.inviteNewUsers('email');
|
||||
expect(groups.Group.invite).to.be.calledOnce;
|
||||
expect(groups.Group.invite).to.be.calledWith({
|
||||
gid: scope.group._id,
|
||||
}, {
|
||||
inviter: user.profile.name,
|
||||
emails: [
|
||||
{name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'},
|
||||
{name: 'Mario', email: 'mario@tmk.com'}
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('uuid', function() {
|
||||
it('invites user with uuid', function() {
|
||||
scope.invitees = [{uuid: '1234'}];
|
||||
|
||||
scope.inviteNewUsers('uuid');
|
||||
expect(groups.Group.invite).to.be.calledOnce;
|
||||
expect(groups.Group.invite).to.be.calledWith({
|
||||
gid: scope.group._id,
|
||||
}, {
|
||||
uuids: ['1234']
|
||||
});
|
||||
});
|
||||
|
||||
it('invites users with uuids', function() {
|
||||
scope.invitees = [{uuid: 'user1'}, {uuid: 'user2'}, {uuid: 'user3'}];
|
||||
|
||||
scope.inviteNewUsers('uuid');
|
||||
expect(groups.Group.invite).to.be.calledOnce;
|
||||
expect(groups.Group.invite).to.be.calledWith({
|
||||
gid: scope.group._id,
|
||||
}, {
|
||||
uuids: ['user1', 'user2', 'user3']
|
||||
});
|
||||
});
|
||||
|
||||
it('resets invitee list after sending', function() {
|
||||
groups.Group.invite.yields();
|
||||
scope.invitees = [{uuid: 'user1'}, {uuid: 'user2'}, {uuid: 'user3'}];
|
||||
|
||||
scope.inviteNewUsers('uuid');
|
||||
|
||||
expect(scope.invitees).to.eql([{uuid: ''}]);
|
||||
});
|
||||
|
||||
it('removes blank fields from being sent', function() {
|
||||
groups.Group.invite.yields();
|
||||
scope.invitees = [{uuid: 'user1'}, {uuid: ''}, {uuid: 'user3'}];
|
||||
|
||||
scope.inviteNewUsers('uuid');
|
||||
|
||||
expect(groups.Group.invite).to.be.calledOnce;
|
||||
expect(groups.Group.invite).to.be.calledWith({
|
||||
gid: scope.group._id,
|
||||
}, {
|
||||
uuids: ['user1', 'user3']
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('invalid invite method', function() {
|
||||
it('logs error', function() {
|
||||
sandbox.stub(console, 'log');
|
||||
|
||||
scope.inviteNewUsers();
|
||||
expect(groups.Group.invite).to.not.be.called;
|
||||
expect(console.log).to.be.calledOnce;
|
||||
expect(console.log).to.be.calledWith('Invalid invite method.');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
describe("Party Controller", function() {
|
||||
var scope, ctrl, user, User, groups, $rootScope, $controller;
|
||||
var scope, ctrl, user, User, groups, rootScope, $controller;
|
||||
|
||||
beforeEach(function() {
|
||||
user = specHelper.newUser(),
|
||||
user._id = "unique-user-id";
|
||||
User = {
|
||||
user: user,
|
||||
sync: sinon.spy
|
||||
sync: sandbox.spy
|
||||
}
|
||||
|
||||
module(function($provide) {
|
||||
|
|
@ -17,7 +17,7 @@ describe("Party Controller", function() {
|
|||
|
||||
inject(function(_$rootScope_, _$controller_, Groups){
|
||||
|
||||
$rootScope = _$rootScope_;
|
||||
rootScope = _$rootScope_;
|
||||
|
||||
scope = _$rootScope_.$new();
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ describe("Party Controller", function() {
|
|||
describe('questAccept', function() {
|
||||
it('calls Groups.questAccept', function() {
|
||||
var party = {};
|
||||
var groupSpy = sinon.stub(groups, "questAccept", function(){return true;});
|
||||
var groupSpy = sandbox.stub(groups, "questAccept", function(){return true;});
|
||||
scope.questAccept(party);
|
||||
groupSpy.should.have.been.calledOnce;
|
||||
});
|
||||
|
|
@ -44,7 +44,7 @@ describe("Party Controller", function() {
|
|||
describe('questReject', function() {
|
||||
it('calls Groups.questReject', function() {
|
||||
var party = {};
|
||||
var groupSpy = sinon.stub(groups, "questReject", function(){return true;});
|
||||
var groupSpy = sandbox.stub(groups, "questReject", function(){return true;});
|
||||
scope.questReject(party);
|
||||
groupSpy.should.have.been.calledOnce;
|
||||
});
|
||||
|
|
@ -54,7 +54,7 @@ describe("Party Controller", function() {
|
|||
var party, cancelSpy, windowSpy;
|
||||
beforeEach(function() {
|
||||
party = {};
|
||||
cancelSpy = sinon.stub(groups, "questCancel", function(){return true;});
|
||||
cancelSpy = sandbox.stub(groups, "questCancel", function(){return true;});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
|
|
@ -63,7 +63,7 @@ describe("Party Controller", function() {
|
|||
});
|
||||
|
||||
it('calls Groups.questCancel when alert box is confirmed', function() {
|
||||
windowSpy = sinon.stub(window, "confirm", function(){return true});
|
||||
windowSpy = sandbox.stub(window, "confirm", function(){return true});
|
||||
|
||||
scope.questCancel(party);
|
||||
windowSpy.should.have.been.calledOnce;
|
||||
|
|
@ -71,8 +71,8 @@ describe("Party Controller", function() {
|
|||
cancelSpy.should.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('does not call Groups.questCancel when alert box is confirmed', function() {
|
||||
windowSpy = sinon.stub(window, "confirm", function(){return false});
|
||||
it('does not call Groups.questCancel when alert box is not confirmed', function() {
|
||||
windowSpy = sandbox.stub(window, "confirm", function(){return false});
|
||||
|
||||
scope.questCancel(party);
|
||||
windowSpy.should.have.been.calledOnce;
|
||||
|
|
@ -84,7 +84,7 @@ describe("Party Controller", function() {
|
|||
var party, abortSpy, windowSpy;
|
||||
beforeEach(function() {
|
||||
party = {};
|
||||
abortSpy = sinon.stub(groups, "questAbort", function(){return true;});
|
||||
abortSpy = sandbox.stub(groups, "questAbort", function(){return true;});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
|
|
@ -93,7 +93,7 @@ describe("Party Controller", function() {
|
|||
});
|
||||
|
||||
it('calls Groups.questAbort when two alert boxes are confirmed', function() {
|
||||
windowSpy = sinon.stub(window, "confirm", function(){return true});
|
||||
windowSpy = sandbox.stub(window, "confirm", function(){return true});
|
||||
|
||||
scope.questAbort(party);
|
||||
windowSpy.should.have.been.calledTwice;
|
||||
|
|
@ -103,7 +103,7 @@ describe("Party Controller", function() {
|
|||
});
|
||||
|
||||
it('does not call Groups.questAbort when first alert box is not confirmed', function() {
|
||||
windowSpy = sinon.stub(window, "confirm", function(){return false});
|
||||
windowSpy = sandbox.stub(window, "confirm", function(){return false});
|
||||
|
||||
scope.questAbort(party);
|
||||
windowSpy.should.have.been.calledOnce;
|
||||
|
|
@ -115,7 +115,7 @@ describe("Party Controller", function() {
|
|||
it('does not call Groups.questAbort when first alert box is confirmed but second one is not', function() {
|
||||
// Hack to confirm first window, but not second
|
||||
var shouldReturn = false;
|
||||
windowSpy = sinon.stub(window, "confirm", function(){
|
||||
windowSpy = sandbox.stub(window, "confirm", function(){
|
||||
shouldReturn = !shouldReturn;
|
||||
return shouldReturn;
|
||||
});
|
||||
|
|
@ -127,4 +127,118 @@ describe("Party Controller", function() {
|
|||
abortSpy.should.not.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#questLeave', function() {
|
||||
var party, leaveSpy, windowSpy;
|
||||
|
||||
beforeEach(function() {
|
||||
party = {};
|
||||
scope.group = {
|
||||
quest: { members: { 'user-id': true } }
|
||||
};
|
||||
leaveSpy = sandbox.stub(groups, 'questLeave').returns({
|
||||
then: sandbox.stub().yields()
|
||||
});
|
||||
});
|
||||
|
||||
it('calls Groups.questLeave when alert box is confirmed', function() {
|
||||
windowSpy = sandbox.stub(window, "confirm").returns(true);
|
||||
|
||||
scope.questLeave(party);
|
||||
windowSpy.should.have.been.calledOnce;
|
||||
windowSpy.should.have.been.calledWith(window.env.t('sureLeave'));
|
||||
leaveSpy.should.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('does not call Groups.questLeave when alert box is not confirmed', function() {
|
||||
windowSpy = sandbox.stub(window, "confirm").returns(false);
|
||||
|
||||
scope.questLeave(party);
|
||||
windowSpy.should.have.been.calledOnce;
|
||||
leaveSpy.should.not.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
describe('clickStartQuest', function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(rootScope, 'openModal');
|
||||
sandbox.stub(rootScope.$state, 'go');
|
||||
});
|
||||
|
||||
it('opens quest modal if user has a quest', function() {
|
||||
user.items.quests = {
|
||||
whale: 1
|
||||
};
|
||||
|
||||
scope.clickStartQuest();
|
||||
|
||||
expect(rootScope.$state.go).to.not.be.called;
|
||||
expect(rootScope.openModal).to.be.calledOnce;
|
||||
expect(rootScope.openModal).to.be.calledWith(
|
||||
'ownedQuests',
|
||||
{ controller: 'InventoryCtrl' }
|
||||
);
|
||||
});
|
||||
|
||||
it('does not open modal if user has no quests', function() {
|
||||
user.items.quests = { };
|
||||
|
||||
scope.clickStartQuest();
|
||||
|
||||
expect(rootScope.openModal).to.not.be.called;
|
||||
expect(rootScope.$state.go).to.be.calledOnce;
|
||||
expect(rootScope.$state.go).to.be.calledWith('options.inventory.quests');
|
||||
});
|
||||
|
||||
it('does not open modal if user had quests previously, but does not now', function() {
|
||||
user.items.quests = {
|
||||
whale: 0,
|
||||
atom1: 0
|
||||
};
|
||||
|
||||
scope.clickStartQuest();
|
||||
|
||||
expect(rootScope.openModal).to.not.be.called;
|
||||
expect(rootScope.$state.go).to.be.calledOnce;
|
||||
expect(rootScope.$state.go).to.be.calledWith('options.inventory.quests');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#leaveOldPartyAndJoinNewParty', function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(scope, 'join');
|
||||
sandbox.stub(groups.Group, 'leave').yields();
|
||||
sandbox.stub(groups, 'party').returns({
|
||||
_id: 'old-party'
|
||||
});
|
||||
sandbox.stub(window, 'confirm').returns(true);
|
||||
});
|
||||
|
||||
it('does nothing if user declines confirmation', function() {
|
||||
window.confirm.returns(false);
|
||||
scope.leaveOldPartyAndJoinNewParty('some-id', 'some-name');
|
||||
|
||||
expect(groups.Group.leave).to.not.be.called;
|
||||
})
|
||||
|
||||
it('leaves user\'s current party', function() {
|
||||
scope.leaveOldPartyAndJoinNewParty('some-id', 'some-name');
|
||||
|
||||
expect(groups.Group.leave).to.be.calledOnce;
|
||||
expect(groups.Group.leave).to.be.calledWith({
|
||||
gid: 'old-party',
|
||||
keep: false
|
||||
});
|
||||
});
|
||||
|
||||
it('joins the new party', function() {
|
||||
scope.leaveOldPartyAndJoinNewParty('some-id', 'some-name');
|
||||
|
||||
expect(scope.join).to.be.calledOnce;
|
||||
expect(scope.join).to.be.calledWith({
|
||||
id: 'some-id',
|
||||
name: 'some-name'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,99 +41,65 @@ describe('groupServices', function() {
|
|||
});
|
||||
|
||||
context('quest function wrappers', function() {
|
||||
var successPromise = function() {
|
||||
return {
|
||||
then: function(success, failure) {
|
||||
success();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var successParty = {
|
||||
$questAccept: successPromise,
|
||||
$questReject: successPromise,
|
||||
$questCancel: successPromise,
|
||||
$questAbort: successPromise
|
||||
}
|
||||
|
||||
var failPromise = function() {
|
||||
return {
|
||||
then: function(success, failure) {
|
||||
failure('fail');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var failParty = {
|
||||
$questAccept: failPromise,
|
||||
$questReject: failPromise,
|
||||
$questCancel: failPromise,
|
||||
$questAbort: failPromise
|
||||
}
|
||||
var successPromise, failPromise;
|
||||
|
||||
beforeEach(function() {
|
||||
sinon.spy(user, 'sync');
|
||||
sinon.stub(console, 'log', function(arg) { return true; });
|
||||
});
|
||||
sandbox.spy(user, 'sync');
|
||||
sandbox.stub(console, 'log');
|
||||
|
||||
afterEach(function() {
|
||||
user.sync.restore();
|
||||
console.log.restore();
|
||||
});
|
||||
|
||||
describe('questAccept', function() {
|
||||
it('syncs user if $questAccept succeeds', function() {
|
||||
groups.questAccept(successParty);
|
||||
user.sync.should.have.been.calledOnce;
|
||||
successPromise = sandbox.stub().returns({
|
||||
then: function(success, failure) {
|
||||
return success();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not sync user if $questAccept fails', function() {
|
||||
groups.questAccept(failParty);
|
||||
user.sync.should.not.have.been.calledOnce;
|
||||
console.log.should.have.been.calledWith('fail');
|
||||
failPromise = sandbox.stub().returns({
|
||||
then: function(success, failure) {
|
||||
return failure('fail');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('questReject', function() {
|
||||
it('syncs user if $questReject succeeds', function() {
|
||||
groups.questReject(successParty);
|
||||
user.sync.should.have.been.calledOnce;
|
||||
console.log.should.not.have.been.called;
|
||||
});
|
||||
var questFunctions = [
|
||||
'questAccept',
|
||||
'questReject',
|
||||
'questCancel',
|
||||
'questAbort',
|
||||
'questLeave'
|
||||
];
|
||||
|
||||
it('does not sync user if $questReject fails', function() {
|
||||
groups.questReject(failParty);
|
||||
user.sync.should.not.have.been.calledOnce;
|
||||
console.log.should.have.been.calledWith('fail');
|
||||
});
|
||||
});
|
||||
for (var i in questFunctions) {
|
||||
var questFunc = questFunctions[i];
|
||||
|
||||
describe('questCancel', function() {
|
||||
it('syncs user if $questCancel succeeds', function() {
|
||||
groups.questCancel(successParty);
|
||||
user.sync.should.have.been.calledOnce;
|
||||
console.log.should.not.have.been.called;
|
||||
});
|
||||
describe('#' + questFunc, function() {
|
||||
it('calls party.$' + questFunc, function() {
|
||||
var party = { };
|
||||
party['$' + questFunc] = successPromise;
|
||||
|
||||
it('does not sync user if $questCancel fails', function() {
|
||||
groups.questCancel(failParty);
|
||||
user.sync.should.not.have.been.calledOnce;
|
||||
console.log.should.have.been.calledWith('fail');
|
||||
});
|
||||
});
|
||||
groups[questFunc](party);
|
||||
|
||||
describe('questAbort', function() {
|
||||
it('syncs user if $questAbort succeeds', function() {
|
||||
groups.questAbort(successParty);
|
||||
user.sync.should.have.been.calledOnce;
|
||||
console.log.should.not.have.been.called;
|
||||
});
|
||||
expect(party['$' + questFunc]).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('does not sync user if $questAbort fails', function() {
|
||||
groups.questAbort(failParty);
|
||||
user.sync.should.not.have.been.calledOnce;
|
||||
console.log.should.have.been.calledWith('fail');
|
||||
it('syncs user if $' + questFunc + ' succeeds', function() {
|
||||
var successParty = { };
|
||||
successParty['$' + questFunc] = successPromise;
|
||||
|
||||
groups[questFunc](successParty);
|
||||
|
||||
user.sync.should.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('does not sync user if $' + questFunc + ' fails', function() {
|
||||
var failParty = { };
|
||||
failParty['$' + questFunc] = failPromise;
|
||||
|
||||
groups[questFunc](failParty);
|
||||
|
||||
user.sync.should.not.have.been.calledOnce;
|
||||
console.log.should.have.been.calledWith('fail');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -67,3 +67,6 @@ li.spaced
|
|||
.flex-column
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.row-margin
|
||||
margin: auto auto 1em auto
|
||||
|
|
|
|||
73
website/public/js/controllers/autoCompleteCtrl.js
Normal file
|
|
@ -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)
|
||||
});
|
||||
}]);
|
||||
139
website/public/js/controllers/chatCtrl.js
Normal file
|
|
@ -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')
|
||||
}
|
||||
|
||||
}]);
|
||||
17
website/public/js/controllers/copyMessageModalCtrl.js
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
]);
|
||||
}]);
|
||||
|
|
|
|||
92
website/public/js/controllers/guildsCtrl.js
Normal file
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
72
website/public/js/controllers/inviteToGroupCtrl.js
Normal file
|
|
@ -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;
|
||||
}
|
||||
}]);
|
||||
132
website/public/js/controllers/partyCtrl.js
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
'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){
|
||||
$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(){
|
||||
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);
|
||||
}
|
||||
}
|
||||
]);
|
||||
10
website/public/js/controllers/tavernCtrl.js
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
|
@ -1,95 +1,114 @@
|
|||
'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) {
|
||||
if (group.type === "party" || $location.$$path === "/options/groups/party") {
|
||||
group.type = 'party';
|
||||
$rootScope.openModal('invite-party', {
|
||||
controller:'InviteToGroupCtrl',
|
||||
resolve: {
|
||||
injectedGroup: function(){ return group; }
|
||||
|
|
@ -99,12 +118,22 @@ function($rootScope, ApiUrl, $resource, $q, $http, User, Challenges, Analytics,
|
|||
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
|
||||
}
|
||||
}
|
||||
}])
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -489,15 +490,12 @@ api.join = function(req, res, next) {
|
|||
api.leave = function(req, res, next) {
|
||||
var user = res.locals.user,
|
||||
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([
|
||||
|
|
@ -534,10 +532,7 @@ api.leave = function(req, res, next) {
|
|||
// 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});
|
||||
|
|
@ -547,11 +542,6 @@ api.leave = function(req, res, next) {
|
|||
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);
|
||||
|
|
@ -580,8 +570,9 @@ var inviteByUUIDs = function(uuids, group, req, res, next){
|
|||
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();
|
||||
});
|
||||
}
|
||||
|
|
@ -603,9 +594,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);
|
||||
|
|
@ -639,14 +627,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){
|
||||
|
|
@ -667,6 +661,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']);
|
||||
|
|
@ -697,9 +692,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
|
||||
|
|
@ -711,11 +706,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"});
|
||||
}
|
||||
}
|
||||
|
|
@ -1075,3 +1070,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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
13
website/views/options/social/party.jade
Normal file
|
|
@ -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')
|
||||
|
||||
9
website/views/options/social/party/party-invitation.jade
Normal file
|
|
@ -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')
|
||||
|
||||
23
website/views/options/social/party/start-a-party.jade
Normal file
|
|
@ -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")')=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')
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ mixin generatedAvatar(options)
|
|||
+costumeSetting('armor', {prefix: "profile.preferences.size + '_' + "})
|
||||
+costumeSetting('back', {suffix: " + '_collar'"})
|
||||
+costumeSetting('body')
|
||||
span(class='head_0')
|
||||
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}}')
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||