mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-08-03 12:40:00 +00:00
Merge branch 'develop' into mountmaster
This commit is contained in:
commit
0fd3f290b8
60 changed files with 1244 additions and 456 deletions
|
|
@ -21,6 +21,7 @@
|
||||||
"NEW_RELIC_APPLICATION_ID":"NEW_RELIC_APPLICATION_ID",
|
"NEW_RELIC_APPLICATION_ID":"NEW_RELIC_APPLICATION_ID",
|
||||||
"NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY",
|
"NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY",
|
||||||
"GA_ID": "GA_ID",
|
"GA_ID": "GA_ID",
|
||||||
|
"FLAG_REPORT_EMAIL": "email@mod.com",
|
||||||
"EMAIL_SERVER": {
|
"EMAIL_SERVER": {
|
||||||
"url": "http://example.com",
|
"url": "http://example.com",
|
||||||
"authUser": "user",
|
"authUser": "user",
|
||||||
|
|
@ -33,13 +34,22 @@
|
||||||
},
|
},
|
||||||
"PAYPAL":{
|
"PAYPAL":{
|
||||||
"billing_plans": {
|
"billing_plans": {
|
||||||
"1":"1",
|
"basic_earned":"basic_earned",
|
||||||
"3":"3",
|
"basic_3mo":"basic_3mo",
|
||||||
"6":"6",
|
"basic_6mo":"basic_6mo",
|
||||||
"12":"12"
|
"google_6mo":"google_6mo",
|
||||||
|
"basic_12mo":"basic_12mo"
|
||||||
},
|
},
|
||||||
"mode":"sandbox",
|
"mode":"sandbox",
|
||||||
"client_id":"client_id",
|
"client_id":"client_id",
|
||||||
"client_secret":"client_secret"
|
"client_secret":"client_secret"
|
||||||
|
},
|
||||||
|
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
|
||||||
|
"LOGGLY": {
|
||||||
|
"enabled": false,
|
||||||
|
"subdomain": "subdomain",
|
||||||
|
"token": "token",
|
||||||
|
"username": "username",
|
||||||
|
"password": "password"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
keys/google/iap-live
Normal file
1
keys/google/iap-live
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk6EVccD7D6y5vLmsUxUXVYTMyByq+NjfTzjMBaKCk93fq8Ahn6Ipha1K4oBIvArUj3CsP5Tb/Yf6c6SrEsSnE3eqgKQgeq+fYyfIm+Fb+tFGTttLonZ2Sqemqeyx2PjOG2CVZBXj1+Zr7jb0ocMNYraYUTvklWnpcK6/5ZaZLLent1PGiU9l5jIfRAsxmJH/UpssgpFPnCs+8PRKUALkJATTfVCCF+PJef6mcKAafAArKUEo0lR2/NeaE62Wpii33DLuKDPfMqL3g4EHZTEVE1ezFl/3WPeLlxuQm38hZLpEfEYKp17qzplU+GmWIfjZt6OdtVdonlYxIxNAZLnWEQIDAQAB
|
||||||
4
migrations/20141211_NaN_consecutives.js
Normal file
4
migrations/20141211_NaN_consecutives.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
db.users.update({'purchased.plan.consecutive.count':NaN}, {$set:{'purchased.plan.consecutive.count':0}}, {multi:1});
|
||||||
|
db.users.update({'purchased.plan.consecutive.offset':NaN}, {$set:{'purchased.plan.consecutive.offset':0}}, {multi:1});
|
||||||
|
db.users.update({'purchased.plan.consecutive.gemCapExtra':NaN}, {$set:{'purchased.plan.consecutive.gemCapExtra':0}}, {multi:1});
|
||||||
|
db.users.update({'purchased.plan.consecutive.trinkets':NaN}, {$set:{'purchased.plan.consecutive.trinkets':0}}, {multi:1});
|
||||||
11
migrations/20141230_new_years_hats.js
Normal file
11
migrations/20141230_new_years_hats.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
db.users.update(
|
||||||
|
{'items.gear.owned.head_special_nye':{$ne:null}},
|
||||||
|
{$set:{'items.gear.owned.head_special_nye2014':false}},
|
||||||
|
{multi:1}
|
||||||
|
)
|
||||||
|
|
||||||
|
db.users.update(
|
||||||
|
{'items.gear.owned.head_special_nye':null},
|
||||||
|
{$set:{'items.gear.owned.head_special_nye':false}},
|
||||||
|
{multi:1}
|
||||||
|
)
|
||||||
|
|
@ -2,7 +2,7 @@ var _id = '';
|
||||||
var update = {
|
var update = {
|
||||||
$push: {
|
$push: {
|
||||||
'purchased.plan.mysteryItems':{
|
'purchased.plan.mysteryItems':{
|
||||||
$each:['head_mystery_201411','weapon_mystery_201411']
|
$each:['head_mystery_201412','armor_mystery_201412']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,11 @@
|
||||||
"grunt-nodemon": "~0.3.0",
|
"grunt-nodemon": "~0.3.0",
|
||||||
"habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared#develop",
|
"habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared#develop",
|
||||||
"icalendar": "git://github.com/lefnire/node-icalendar#master",
|
"icalendar": "git://github.com/lefnire/node-icalendar#master",
|
||||||
|
"in-app-purchase": "^0.2.0",
|
||||||
"jade": "~1.7.0",
|
"jade": "~1.7.0",
|
||||||
"js2xmlparser": "~0.1.2",
|
"js2xmlparser": "~0.1.2",
|
||||||
"lodash": "~2.4.1",
|
"lodash": "~2.4.1",
|
||||||
|
"loggly": "~1.0.8",
|
||||||
"method-override": "~2.2.0",
|
"method-override": "~2.2.0",
|
||||||
"moment": "~2.8.3",
|
"moment": "~2.8.3",
|
||||||
"mongoose": "~3.8.17",
|
"mongoose": "~3.8.17",
|
||||||
|
|
|
||||||
|
|
@ -30,17 +30,19 @@ future re: pets and whatnot, this is just temporary.
|
||||||
.avatar-level, .avatar-name
|
.avatar-level, .avatar-name
|
||||||
position: absolute
|
position: absolute
|
||||||
@extend $hrpg-label
|
@extend $hrpg-label
|
||||||
|
z-index: 2
|
||||||
.avatar-level
|
.avatar-level
|
||||||
bottom: 2px
|
bottom: 4px
|
||||||
right: 2px
|
right: 4px
|
||||||
// when a buff/rebirth exists, add space between the icon and the level
|
// when a buff/rebirth exists, add space between the icon and the level
|
||||||
.glyphicon-circle-arrow-up
|
.glyphicon-circle-arrow-up
|
||||||
padding-right: 4px
|
padding-right: 4px
|
||||||
.glyphicon-plus-sign
|
.glyphicon-plus-sign
|
||||||
padding-left: 4px
|
padding-left: 4px
|
||||||
.avatar-name
|
.avatar-name
|
||||||
top: 2px
|
max-width: 132px
|
||||||
left: 2px
|
top: 4px
|
||||||
|
left: 4px
|
||||||
opacity: 0 // Hide username by default
|
opacity: 0 // Hide username by default
|
||||||
transition: opacity 0.2s ease-out
|
transition: opacity 0.2s ease-out
|
||||||
|
|
||||||
|
|
@ -83,8 +85,9 @@ future re: pets and whatnot, this is just temporary.
|
||||||
width: 50px !important
|
width: 50px !important
|
||||||
.addthis_native_toolbox
|
.addthis_native_toolbox
|
||||||
position: absolute
|
position: absolute
|
||||||
top:2px
|
bottom:4px
|
||||||
right:2px
|
left:4px
|
||||||
|
z-index: 2
|
||||||
opacity:0
|
opacity:0
|
||||||
&:hover .addthis_native_toolbox
|
&:hover .addthis_native_toolbox
|
||||||
opacity:1
|
opacity:1
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,9 @@
|
||||||
markdown, span.time
|
markdown, span.time
|
||||||
padding: 0 3px
|
padding: 0 3px
|
||||||
line-height: 1.618em
|
line-height: 1.618em
|
||||||
|
|
||||||
|
.long-title .tooltip
|
||||||
|
white-space: normal
|
||||||
|
|
||||||
span.time
|
span.time
|
||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ hrpg-button-color-mixin($hrpg-button-color)
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
$hrpg-button-master
|
$hrpg-button-master
|
||||||
list-style: none
|
list-style: none
|
||||||
> a, > button, > input
|
> a, > button, > input, label::after
|
||||||
display: inline-block !important
|
display: inline-block !important
|
||||||
padding: 0.25em 0.5em !important
|
padding: 0.25em 0.5em !important
|
||||||
text-decoration:none
|
text-decoration:none
|
||||||
|
|
@ -86,7 +86,7 @@ $hrpg-button-master
|
||||||
background-color: #aaa !important
|
background-color: #aaa !important
|
||||||
$hrpg-button
|
$hrpg-button
|
||||||
@extend $hrpg-button-master
|
@extend $hrpg-button-master
|
||||||
> a, > button, > input
|
> a, > button, > input, label::after
|
||||||
border: 1px solid #ccc !important
|
border: 1px solid #ccc !important
|
||||||
border-radius: 0.382em !important
|
border-radius: 0.382em !important
|
||||||
$hrpg-button-call-to-action
|
$hrpg-button-call-to-action
|
||||||
|
|
@ -111,7 +111,7 @@ $hrpg-button-with-input
|
||||||
height: 2em
|
height: 2em
|
||||||
input
|
input
|
||||||
border: 1px solid #ccc
|
border: 1px solid #ccc
|
||||||
border-radius: 0.382em 0em 0em 0.382em
|
border-radius: 0.382em 0em 0em 0.382em !important
|
||||||
padding-left: 0.618em
|
padding-left: 0.618em
|
||||||
background-color: #fff !important
|
background-color: #fff !important
|
||||||
-webkit-appearance: none
|
-webkit-appearance: none
|
||||||
|
|
@ -150,15 +150,17 @@ $hrpg-button-bar
|
||||||
border-right: 1px solid darken(#fff,3.82%)
|
border-right: 1px solid darken(#fff,3.82%)
|
||||||
float:left
|
float:left
|
||||||
word-wrap: break-word
|
word-wrap: break-word
|
||||||
|
button
|
||||||
|
border: none
|
||||||
li:nth-of-type(2)
|
li:nth-of-type(2)
|
||||||
border-radius: 0.382em 0em 0em 0.382em
|
border-radius: 0.382em 0em 0em 0.382em
|
||||||
li:first-of-type
|
li:first-of-type
|
||||||
border-radius: 0.382em 0em 0em 0.382em
|
border-radius: 0.382em 0em 0em 0.382em
|
||||||
a
|
a, button
|
||||||
border-radius: 0.382em 0em 0em 0.382em
|
border-radius: 0.382em 0em 0em 0.382em
|
||||||
li:last-of-type
|
li:last-of-type
|
||||||
border-right: none
|
border-right: none
|
||||||
a
|
a, button
|
||||||
border-radius: 0em 0.382em 0.382em 0em
|
border-radius: 0em 0.382em 0.382em 0em
|
||||||
|
|
||||||
$hrpg-button-bar-with-heading
|
$hrpg-button-bar-with-heading
|
||||||
|
|
|
||||||
|
|
@ -32,4 +32,33 @@
|
||||||
visibility: hidden
|
visibility: hidden
|
||||||
|
|
||||||
.transparent
|
.transparent
|
||||||
opacity: 0.5
|
opacity: 0.5
|
||||||
|
|
||||||
|
// Allow block element to be centered
|
||||||
|
.col-centered
|
||||||
|
margin-left: auto
|
||||||
|
margin-right: auto
|
||||||
|
|
||||||
|
// .pull-right & .pull-left for specific screen sizes
|
||||||
|
// Follows conventions for col-x-SIZE classes
|
||||||
|
|
||||||
|
@media (min-width: 768px)
|
||||||
|
.pull-left-sm
|
||||||
|
float: left
|
||||||
|
|
||||||
|
.pull-right-sm
|
||||||
|
float: right
|
||||||
|
|
||||||
|
@media (min-width: 992px)
|
||||||
|
.pull-left-md
|
||||||
|
float: left
|
||||||
|
|
||||||
|
.pull-right-md
|
||||||
|
float: right
|
||||||
|
|
||||||
|
@media (min-width: 1200px)
|
||||||
|
.pull-left-lg
|
||||||
|
float: left
|
||||||
|
|
||||||
|
.pull-right-lg
|
||||||
|
float: right
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ html, body
|
||||||
@media (max-width: 600px)
|
@media (max-width: 600px)
|
||||||
#wrap
|
#wrap
|
||||||
margin-top: 0
|
margin-top: 0
|
||||||
|
padding: 0 4px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
/* Gems
|
/* Gems
|
||||||
-------------------------------------------------- */
|
-------------------------------------------------- */
|
||||||
|
|
@ -135,6 +137,9 @@ a
|
||||||
a span.glyphicon
|
a span.glyphicon
|
||||||
color: #333 // Takes base color from bootstrap
|
color: #333 // Takes base color from bootstrap
|
||||||
|
|
||||||
|
&.text-danger
|
||||||
|
color: #a94442 // Allows glyphicon to use text-danger class
|
||||||
|
|
||||||
a.label
|
a.label
|
||||||
color: #fff
|
color: #fff
|
||||||
|
|
||||||
|
|
@ -176,3 +181,5 @@ a.label
|
||||||
|
|
||||||
.white, .white a
|
.white, .white a
|
||||||
color: #fff !important
|
color: #fff !important
|
||||||
|
.line-through
|
||||||
|
text-decoration line-through
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
line-height: 1.5em
|
line-height: 1.5em
|
||||||
padding-left: 0.75em
|
padding-left: 0.75em
|
||||||
|
|
||||||
.shop_gold, .shop_silver, .shop_copper
|
.shop_gold, .shop_silver, .shop_copper, .shop_spell
|
||||||
vertical-align: top
|
vertical-align: top
|
||||||
display: inline-block
|
display: inline-block
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
.quest_collected_true
|
.quest_collected_true
|
||||||
color: #ccc
|
color: #ccc
|
||||||
text-decoration:line-through
|
text-decoration:line-through
|
||||||
|
|
||||||
|
quest-rewards
|
||||||
|
hr
|
||||||
|
clear:both
|
||||||
|
|
|
||||||
|
|
@ -48,5 +48,3 @@ a.hint:hover
|
||||||
.tasks-lists .row .col-md-3
|
.tasks-lists .row .col-md-3
|
||||||
padding-right: 0
|
padding-right: 0
|
||||||
padding-left: 0
|
padding-left: 0
|
||||||
.task-column
|
|
||||||
padding: 0
|
|
||||||
|
|
|
||||||
|
|
@ -8,29 +8,99 @@ $stages = (worst $worst) (worse $worse) (bad $bad) (neutral $neutral) (good $goo
|
||||||
|
|
||||||
// for each color stage, generate a named class w/ the appropriate color
|
// for each color stage, generate a named class w/ the appropriate color
|
||||||
for $stage in $stages
|
for $stage in $stages
|
||||||
.color-{$stage[0]}
|
.task-column:not(.rewards)
|
||||||
background-color: $stage[1]
|
.color-{$stage[0]}:not(.completed)
|
||||||
.action-yesno label,
|
|
||||||
.task-action-btn
|
|
||||||
background-color: darken($stage[1], 30%)
|
|
||||||
&:hover, &:focus
|
|
||||||
background-color: darken($stage[1], 40%)
|
|
||||||
.task-options
|
|
||||||
background-color: $stage[1]
|
background-color: $stage[1]
|
||||||
|
border: 1px solid shade($stage[1],10%)
|
||||||
|
.priority-multiplier, .task-attributes, .repeat-days
|
||||||
|
li
|
||||||
|
hrpg-button-color-mixin($stage[1])
|
||||||
|
button
|
||||||
|
&.active
|
||||||
|
box-shadow: inset 0 0 0 1px darken($stage[1],40%) !important
|
||||||
|
background-color: darken($stage[1],5%) !important
|
||||||
|
&:focus
|
||||||
|
border: 1px solid darken($stage[1],30%)
|
||||||
|
outline: 0
|
||||||
|
.plusminus
|
||||||
|
.task-checker
|
||||||
|
label:after
|
||||||
|
border: 1px solid darken($stage[1], 30%) !important
|
||||||
|
input[type=checkbox]:checked + label:after
|
||||||
|
//border: 1px solid darken($stage[1], 50%) !important
|
||||||
|
box-shadow: inset 0 0 0 1px darken($stage[1],40%) !important
|
||||||
|
background-color: darken($stage[1],5%) !important
|
||||||
|
.save-close, .task-checklist-edit li
|
||||||
|
hrpg-button-color-mixin(darken($stage[1],5%))
|
||||||
|
button
|
||||||
|
&:focus
|
||||||
|
border: 1px solid darken($stage[1],30%)
|
||||||
|
outline: 0
|
||||||
|
.action-yesno label,
|
||||||
|
.task-action-btn
|
||||||
|
background-color: darken($stage[1], 30%)
|
||||||
|
&:hover, &:focus
|
||||||
|
background-color: darken($stage[1], 40%)
|
||||||
|
.task-options
|
||||||
|
background-color: $stage[1]
|
||||||
|
.option-group:not(.task-checklist)
|
||||||
|
border-bottom: 1px solid darken($stage[1], 15%)
|
||||||
|
.option-content
|
||||||
|
border-color: darken($stage[1], 16.18%) !important
|
||||||
|
&:hover
|
||||||
|
border-color: darken($stage[1], 32.8%) !important
|
||||||
|
&:focus
|
||||||
|
border-color: darken($stage[1], 61.8%) !important
|
||||||
|
outline: none;
|
||||||
|
|
||||||
// completed has to be outside the loop to override the color class
|
// completed has to be outside the loop to override the color class
|
||||||
.completed
|
.completed
|
||||||
|
color: darken($completed,30%)
|
||||||
background-color: $completed
|
background-color: $completed
|
||||||
color: #999
|
border: 1px solid shade($completed,10%)
|
||||||
.glyphicon
|
.priority-multiplier, .task-attributes, .repeat-days
|
||||||
color: #333
|
li
|
||||||
.task-checker label
|
hrpg-button-color-mixin($completed)
|
||||||
background-color: darken($completed, 30%)
|
button
|
||||||
|
&.active
|
||||||
|
box-shadow: inset 0 0 0 1px darken($completed,40%) !important
|
||||||
|
background-color: darken($completed,5%) !important
|
||||||
|
&:focus
|
||||||
|
border: 1px solid darken($completed,30%)
|
||||||
|
outline: 0
|
||||||
|
.plusminus
|
||||||
|
.task-checker
|
||||||
|
label:after
|
||||||
|
border: 1px solid darken($completed, 30%) !important
|
||||||
|
input[type=checkbox]:checked + label:after
|
||||||
|
box-shadow: inset 0 0 0 1px darken($completed,40%) !important
|
||||||
|
background-color: darken($completed,5%) !important
|
||||||
|
.save-close, .task-checklist-edit li
|
||||||
|
hrpg-button-color-mixin(darken($completed,5%))
|
||||||
|
button
|
||||||
|
&:focus
|
||||||
|
border: 1px solid darken($completed,30%)
|
||||||
|
outline: 0
|
||||||
|
.action-yesno label,
|
||||||
.task-action-btn
|
.task-action-btn
|
||||||
background-color: darken($completed, 30%)
|
background-color: darken($completed, 30%)
|
||||||
|
&:hover, &:focus
|
||||||
|
background-color: darken($completed, 40%)
|
||||||
|
.task-options
|
||||||
|
background-color: $completed
|
||||||
|
.option-group:not(.task-checklist)
|
||||||
|
border-bottom: 1px solid darken($completed, 15%)
|
||||||
|
.option-content
|
||||||
|
border-color: darken($completed, 16.18%) !important
|
||||||
|
&:hover
|
||||||
|
border-color: darken($completed, 32.8%) !important
|
||||||
|
&:focus
|
||||||
|
border-color: darken($completed, 61.8%) !important
|
||||||
|
outline: 0;
|
||||||
|
|
||||||
.reward
|
.reward, .reward-item
|
||||||
background-color: white
|
background-color: white
|
||||||
|
border: 1px solid $completed !important
|
||||||
|
|
||||||
// 2. Columns & Tasks
|
// 2. Columns & Tasks
|
||||||
// ===================
|
// ===================
|
||||||
|
|
@ -70,7 +140,7 @@ for $stage in $stages
|
||||||
// "Add new task" form
|
// "Add new task" form
|
||||||
// -----------------
|
// -----------------
|
||||||
.task-add
|
.task-add
|
||||||
margin-top: 1.618em
|
margin-top: 1.5em
|
||||||
@extend $hrpg-button-with-input
|
@extend $hrpg-button-with-input
|
||||||
hrpg-button-color-mixin($color-options-submenu)
|
hrpg-button-color-mixin($color-options-submenu)
|
||||||
input, button
|
input, button
|
||||||
|
|
@ -90,7 +160,7 @@ for $stage in $stages
|
||||||
padding: 0
|
padding: 0
|
||||||
min-height: 3em
|
min-height: 3em
|
||||||
margin-bottom: 0.75em
|
margin-bottom: 0.75em
|
||||||
box-shadow: inset 0 0 1px rgba(0,0,0,0.5)
|
// box-shadow: inset 0 0 1px rgba(0,0,0,0.5)
|
||||||
position: relative
|
position: relative
|
||||||
&:hover
|
&:hover
|
||||||
cursor: move
|
cursor: move
|
||||||
|
|
@ -99,6 +169,16 @@ for $stage in $stages
|
||||||
|
|
||||||
label
|
label
|
||||||
font-weight: 400
|
font-weight: 400
|
||||||
|
|
||||||
|
input[type="text"], input[type="number"], textarea.option-content
|
||||||
|
border: 1px solid #aaa
|
||||||
|
border-radius: 0.382em
|
||||||
|
padding-left: 0.618em
|
||||||
|
background-color: #fff !important
|
||||||
|
-webkit-appearance: none
|
||||||
|
-moz-appearance: none
|
||||||
|
appearance: none
|
||||||
|
box-shadow: none //remove red glow in Firefox
|
||||||
|
|
||||||
// task content
|
// task content
|
||||||
.task-text
|
.task-text
|
||||||
|
|
@ -107,7 +187,7 @@ for $stage in $stages
|
||||||
line-height: 1.4
|
line-height: 1.4
|
||||||
word-wrap: break-word
|
word-wrap: break-word
|
||||||
|
|
||||||
markdown > ul > li // Otherwise, + gets turned into a bullet and gets maksed by buttons
|
markdown > ul > li // Otherwise, + gets turned into a bullet and gets masked by buttons
|
||||||
margin-left:20px
|
margin-left:20px
|
||||||
|
|
||||||
span.emoji
|
span.emoji
|
||||||
|
|
@ -118,6 +198,8 @@ for $stage in $stages
|
||||||
|
|
||||||
.habit-wide .task-text
|
.habit-wide .task-text
|
||||||
padding-left: 7em
|
padding-left: 7em
|
||||||
|
.habit-narrow .task-text
|
||||||
|
padding-left: 0.75em
|
||||||
|
|
||||||
// when a task is being dragged
|
// when a task is being dragged
|
||||||
.task.ui-sortable-helper
|
.task.ui-sortable-helper
|
||||||
|
|
@ -174,28 +256,21 @@ for $stage in $stages
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
|
|
||||||
// plus/minus checkbox
|
// plus/minus checkbox
|
||||||
.action-plusminus
|
.plusminus
|
||||||
label
|
text-align: center
|
||||||
margin-right: 1.5em
|
.task-checker
|
||||||
label:after
|
font-size: 1.5em // 1.41em
|
||||||
font-size: 1.41em
|
@extend $hrpg-button
|
||||||
width: 2.12765em
|
margin-bottom: 0.5em
|
||||||
height: 2.12765em
|
&:first-of-type
|
||||||
line-height: 2.12765
|
margin-right: 0.5em
|
||||||
text-align: center
|
label:after
|
||||||
display: inline-block
|
width: 2em
|
||||||
vertical-align top
|
line-height: 1.5
|
||||||
opacity: 0.3
|
label[for$="plus"]:after
|
||||||
background-color: #ddd
|
content: '+'
|
||||||
outline: 1px solid rgba(0,0,0,0.5)
|
label[for$="minus"]:after
|
||||||
color: #222
|
content: '−'
|
||||||
label[for$="plus"]:after
|
|
||||||
content: '+'
|
|
||||||
label[for$="minus"]:after
|
|
||||||
content: '−'
|
|
||||||
|
|
||||||
&.select-toggle input[type=checkbox]:checked + label:after
|
|
||||||
opacity: 1
|
|
||||||
|
|
||||||
// yes/no checkbox
|
// yes/no checkbox
|
||||||
.action-yesno
|
.action-yesno
|
||||||
|
|
@ -227,9 +302,9 @@ for $stage in $stages
|
||||||
label:hover:after,
|
label:hover:after,
|
||||||
label:focus:after
|
label:focus:after
|
||||||
content: "\E013"
|
content: "\E013"
|
||||||
font-family 'Glyphicons Halflings'
|
font-family: "Glyphicons Halflings"
|
||||||
border none
|
border: none
|
||||||
margin 0
|
margin: 0
|
||||||
line-height: 1.714285714em
|
line-height: 1.714285714em
|
||||||
height: 1.714285714em
|
height: 1.714285714em
|
||||||
width: 1.714285714em
|
width: 1.714285714em
|
||||||
|
|
@ -241,9 +316,9 @@ for $stage in $stages
|
||||||
// completed icon
|
// completed icon
|
||||||
input[type=checkbox]:checked + label:after
|
input[type=checkbox]:checked + label:after
|
||||||
content: "\E013"
|
content: "\E013"
|
||||||
font-family 'Glyphicons Halflings'
|
font-family: "Glyphicons Halflings"
|
||||||
border none
|
border: none
|
||||||
margin 0
|
margin: 0
|
||||||
line-height: 1.714285714em
|
line-height: 1.714285714em
|
||||||
height: 1.714285714em
|
height: 1.714285714em
|
||||||
width: 1.714285714em
|
width: 1.714285714em
|
||||||
|
|
@ -295,36 +370,36 @@ form
|
||||||
//background-color:
|
//background-color:
|
||||||
|
|
||||||
.option-group
|
.option-group
|
||||||
border-bottom: 1px solid rgba(0,0,0,0.1)
|
//border-bottom: 1px solid rgba(0,0,0,0.1)
|
||||||
padding: 0 0 1em
|
padding: 0 0 1em
|
||||||
margin-bottom: 1em
|
margin-bottom: 1em
|
||||||
|
|
||||||
.option-title
|
.option-title
|
||||||
font-size: 1em
|
font-size: 1em
|
||||||
margin: 0 0 0.5em
|
margin: 0 0 0.5em
|
||||||
line-height: 1.5
|
line-height: 2.5
|
||||||
border: 0
|
border: 0
|
||||||
padding: 0
|
padding: 0
|
||||||
color: #333
|
//color: #333
|
||||||
font-style: italic
|
//font-style: italic
|
||||||
&:not(.mega):after
|
font-weight: bold
|
||||||
content: ':'
|
text-align: center
|
||||||
&.mega
|
&.mega
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
font-style: normal
|
//font-style: normal
|
||||||
font-weight: bold
|
&:after
|
||||||
text-decoration: underline
|
font-family: "Glyphicons Halflings"
|
||||||
|
font-size: 0.75em
|
||||||
|
content: "\E114"
|
||||||
|
padding-left: 0.75em
|
||||||
|
//&:not(.mega):after
|
||||||
|
//content: ':'
|
||||||
|
|
||||||
.option-content
|
.option-content
|
||||||
height: 2.5em
|
height: 2.5em
|
||||||
width: 100%
|
width: 100%
|
||||||
margin: 0 0 1em
|
margin: 0 0 1em
|
||||||
padding: 0 0 0 0.5em
|
padding: 0 0 0 0.5em
|
||||||
outline: 0
|
|
||||||
border: 1px solid rgba(0,0,0,0.2)
|
|
||||||
box-shadow: none
|
|
||||||
border-radius: 0
|
|
||||||
font-family: 'Lato', sans-serif //bootstrap override
|
|
||||||
|
|
||||||
textarea.option-content
|
textarea.option-content
|
||||||
height: 5em
|
height: 5em
|
||||||
|
|
@ -393,47 +468,66 @@ form
|
||||||
.task-options
|
.task-options
|
||||||
.task-action-btn.tile
|
.task-action-btn.tile
|
||||||
margin: 3px
|
margin: 3px
|
||||||
|
|
||||||
|
form
|
||||||
|
padding-bottom: 1em
|
||||||
|
.priority-multiplier, .task-attributes, .repeat-days
|
||||||
|
text-align: center
|
||||||
|
li
|
||||||
|
@extend $hrpg-button
|
||||||
|
margin-bottom: 1em
|
||||||
|
margin-right: 0.75em
|
||||||
|
display: inline-block
|
||||||
|
&:last-of-type
|
||||||
|
margin-right: 0
|
||||||
|
.repeat-days
|
||||||
|
li
|
||||||
|
button
|
||||||
|
min-width: 2.5em
|
||||||
|
.save-close
|
||||||
|
text-align: center
|
||||||
|
@extend $hrpg-button
|
||||||
// todos ui
|
// todos ui
|
||||||
// --------
|
// --------
|
||||||
|
|
||||||
// Checklists
|
// Checklists
|
||||||
// --------
|
// --------
|
||||||
.checklist-form
|
.task-checklist-edit
|
||||||
/* Make an input blend into its parent */
|
> ul
|
||||||
input.inline-edit
|
text-align: center
|
||||||
reset-box-model()
|
li
|
||||||
reset-font()
|
@extend $hrpg-button
|
||||||
line-height: inherit
|
button
|
||||||
text-align: inherit
|
span
|
||||||
background-color:inherit
|
margin-right: 0.5em
|
||||||
box-shadow: none
|
> form
|
||||||
width: 80%
|
li
|
||||||
|
@extend $hrpg-button-with-input
|
||||||
|
@extend $clearfix
|
||||||
|
input
|
||||||
|
width: 70%
|
||||||
|
|
||||||
/* Add interaction cues on hover and focus */
|
/* Add interaction cues on hover and focus */
|
||||||
input.inline-edit:hover,input.inline-edit:focus
|
&:hover,&:focus
|
||||||
background-color: #FFD
|
background-color: #FFD
|
||||||
transition: background-color 0.5s
|
transition: background-color 0.5s
|
||||||
|
|
||||||
.checklist-icons
|
.checklist-icon
|
||||||
opacity:0
|
opacity:0.25
|
||||||
width: 5%
|
text-align: center
|
||||||
|
line-height: 1.5
|
||||||
li
|
float: left
|
||||||
min-height: 20px
|
padding: 0.25em 0.5em
|
||||||
padding-left: 10px
|
|
||||||
margin-bottom: 5px
|
li
|
||||||
|
margin-bottom: 1em
|
||||||
[type="checkbox"]
|
&:hover .checklist-icon
|
||||||
margin: 0px 10px 3px 0px
|
opacity:1
|
||||||
width: 4%
|
|
||||||
li:hover .checklist-icons
|
|
||||||
opacity:1
|
|
||||||
|
|
||||||
// Task filters
|
// Task filters
|
||||||
// --------
|
// --------
|
||||||
.task-filter
|
.task-filter
|
||||||
margin: 1.618em 0 1em 0
|
margin: 1.5em 0 1em 0
|
||||||
hrpg-button-bar-mixin($color-tasks)
|
hrpg-button-bar-mixin($color-tasks)
|
||||||
@extend $hrpg-button-bar
|
@extend $hrpg-button-bar
|
||||||
width: 100%
|
width: 100%
|
||||||
|
|
@ -449,16 +543,18 @@ form
|
||||||
width: 100%
|
width: 100%
|
||||||
height: 100%
|
height: 100%
|
||||||
.rewards
|
.rewards
|
||||||
|
.task
|
||||||
|
min-height: 3.07em // Temp fix for slight misalignment of btn-buy
|
||||||
.task-filter
|
.task-filter
|
||||||
li:nth-child(3n + 1):nth-last-child(2)
|
li:nth-child(3n + 1):nth-last-child(2)
|
||||||
width: 33.333%
|
width: 33.333%
|
||||||
li:nth-child(3n + 1):nth-last-child(2) + li
|
li:nth-child(3n + 1):nth-last-child(2) + li
|
||||||
width: 66.666%
|
width: 66.666%
|
||||||
|
|
||||||
.btn-buy
|
.btn-buy
|
||||||
word-wrap: break-word
|
word-wrap: break-word
|
||||||
padding-left: 0.2em
|
padding-left: 0.2em
|
||||||
text-align: left
|
text-align: left
|
||||||
|
border:none
|
||||||
span
|
span
|
||||||
position: relative
|
position: relative
|
||||||
left: -2px
|
left: -2px
|
||||||
|
|
@ -474,9 +570,4 @@ form
|
||||||
margin: auto
|
margin: auto
|
||||||
.shop_gold + .reward-cost
|
.shop_gold + .reward-cost
|
||||||
line-height: 1
|
line-height: 1
|
||||||
top: -0.1em
|
top: -0.1em
|
||||||
|
|
||||||
.repeat-days
|
|
||||||
a
|
|
||||||
border-radius: 0.382em
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ window.habitrpg = angular.module('habitrpg',
|
||||||
|
|
||||||
// @see https://github.com/angular-ui/ui-router/issues/110 and https://github.com/HabitRPG/habitrpg/issues/1705
|
// @see https://github.com/angular-ui/ui-router/issues/110 and https://github.com/HabitRPG/habitrpg/issues/1705
|
||||||
// temporary hack until they have a better solution
|
// temporary hack until they have a better solution
|
||||||
.value('$anchorScroll', angular.noop)
|
|
||||||
|
|
||||||
.constant("API_URL", "")
|
.constant("API_URL", "")
|
||||||
.constant("STORAGE_USER_ID", 'habitrpg-user')
|
.constant("STORAGE_USER_ID", 'habitrpg-user')
|
||||||
|
|
@ -185,6 +184,10 @@ window.habitrpg = angular.module('habitrpg',
|
||||||
url: '/timetravelers',
|
url: '/timetravelers',
|
||||||
templateUrl: "partials/options.inventory.timetravelers.html"
|
templateUrl: "partials/options.inventory.timetravelers.html"
|
||||||
})
|
})
|
||||||
|
.state('options.inventory.seasonalshop', {
|
||||||
|
url: '/seasonalshop',
|
||||||
|
templateUrl: "partials/options.inventory.seasonalshop.html"
|
||||||
|
})
|
||||||
|
|
||||||
// Options > Settings
|
// Options > Settings
|
||||||
.state('options.settings', {
|
.state('options.settings', {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@
|
||||||
//$.getScript("//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5016f6cc44ad68a4"); //FIXME why isn't this working when here? instead it's now in <head>
|
//$.getScript("//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5016f6cc44ad68a4"); //FIXME why isn't this working when here? instead it's now in <head>
|
||||||
var addthisServices = 'facebook,twitter,googleplus,tumblr,'+window.env.BASE_URL.replace('https://','').replace('http://','');
|
var addthisServices = 'facebook,twitter,googleplus,tumblr,'+window.env.BASE_URL.replace('https://','').replace('http://','');
|
||||||
window.addthis_config = {
|
window.addthis_config = {
|
||||||
|
ui_click: true,
|
||||||
services_custom:{
|
services_custom:{
|
||||||
name: "Download",
|
name: "Download",
|
||||||
url: window.env.BASE_URL+"/export/avatar-"+User.user._id+".png",
|
url: window.env.BASE_URL+"/export/avatar-"+User.user._id+".png",
|
||||||
|
|
|
||||||
|
|
@ -108,8 +108,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
.controller("MemberModalCtrl", ['$scope', '$rootScope', 'Members', 'Shared', '$http', 'Notification',
|
.controller("MemberModalCtrl", ['$scope', '$rootScope', 'Members', 'Shared', '$http', 'Notification', 'Groups',
|
||||||
function($scope, $rootScope, Members, Shared, $http, Notification) {
|
function($scope, $rootScope, Members, Shared, $http, Notification, Groups) {
|
||||||
$scope.timestamp = function(timestamp){
|
$scope.timestamp = function(timestamp){
|
||||||
return moment(timestamp).format('MM/DD/YYYY');
|
return moment(timestamp).format('MM/DD/YYYY');
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +129,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||||
$scope.gift = {
|
$scope.gift = {
|
||||||
type: 'gems',
|
type: 'gems',
|
||||||
gems: {amount:0, fromBalance:true},
|
gems: {amount:0, fromBalance:true},
|
||||||
subscription: {months:0},
|
subscription: {key:''},
|
||||||
message:''
|
message:''
|
||||||
};
|
};
|
||||||
$scope.sendGift = function(uuid, gift){
|
$scope.sendGift = function(uuid, gift){
|
||||||
|
|
@ -139,6 +139,20 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||||
$scope.$close();
|
$scope.$close();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
$scope.reportAbuse = function(reporter, message, groupId) {
|
||||||
|
message.flags[reporter._id] = true;
|
||||||
|
Groups.Group.flagChatMessage({gid: groupId, messageId: message.id}, undefined, function(data){
|
||||||
|
Notification.text(window.env.t('abuseReported'));
|
||||||
|
$scope.$close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$scope.clearFlagCount = function(message, groupId) {
|
||||||
|
Groups.Group.clearFlagCount({gid: groupId, messageId: message.id}, undefined, function(data){
|
||||||
|
message.flagCount = 0;
|
||||||
|
Notification.text("Flags cleared");
|
||||||
|
$scope.$close();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
@ -197,7 +211,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||||
});
|
});
|
||||||
}])
|
}])
|
||||||
|
|
||||||
.controller('ChatCtrl', ['$scope', 'Groups', 'User', '$http', 'ApiUrlService', 'Notification', function($scope, Groups, User, $http, ApiUrlService, Notification){
|
.controller('ChatCtrl', ['$scope', 'Groups', 'User', '$http', 'ApiUrlService', 'Notification', 'Members', '$rootScope', function($scope, Groups, User, $http, ApiUrlService, Notification, Members, $rootScope){
|
||||||
$scope.message = {content:''};
|
$scope.message = {content:''};
|
||||||
$scope._sending = false;
|
$scope._sending = false;
|
||||||
|
|
||||||
|
|
@ -239,12 +253,14 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||||
$scope.deleteChatMessage = function(group, message){
|
$scope.deleteChatMessage = function(group, message){
|
||||||
if(message.uuid === User.user.id || (User.user.backer && User.user.contributor.admin)){
|
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;
|
var previousMsg = (group.chat && group.chat[0]) ? group.chat[0].id : false;
|
||||||
Groups.Group.deleteChatMessage({gid:group._id, messageId:message.id, previousMsg:previousMsg}, undefined, function(data){
|
if(confirm('Are you sure you want to delete this message?')){
|
||||||
if(data.chat) group.chat = data.chat;
|
Groups.Group.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});
|
var i = _.findIndex(group.chat, {id: message.id});
|
||||||
if(i !== -1) group.chat.splice(i, 1);
|
if(i !== -1) group.chat.splice(i, 1);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,6 +278,22 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||||
$http.post(ApiUrlService.get() + '/api/v2/groups/' + group._id + '/chat/' + message.id + '/like');
|
$http.post(ApiUrlService.get() + '/api/v2/groups/' + group._id + '/chat/' + message.id + '/like');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$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.sync = function(group){
|
$scope.sync = function(group){
|
||||||
group.$get();
|
group.$get();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,16 +88,23 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User',
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.purchase = function(type, item){
|
$scope.purchase = function(type, item){
|
||||||
if (item.key=='spookDust') return User.user.ops.buySpookDust({});
|
if (type == 'special') return User.user.ops.buySpecialSpell({params:{key:item.key}});
|
||||||
|
|
||||||
var gems = User.user.balance * 4;
|
var gems = User.user.balance * 4;
|
||||||
|
|
||||||
if(gems < item.value) return $rootScope.openModal('buyGems');
|
var string = (type == 'weapon') ? window.env.t('weapon') : (type == 'armor') ? window.env.t('armor') : (type == 'head') ? window.env.t('headgear') : (type == 'shield') ? window.env.t('offhand') : (type == 'hatchingPotions') ? window.env.t('hatchingPotion') : (type == 'eggs') ? window.env.t('eggSingular') : (type == 'quests') ? window.env.t('quest') : (item.key == 'Saddle') ? window.env.t('foodSaddleText').toLowerCase() : type; // this is ugly but temporary, once the purchase modal is done this will be removed
|
||||||
var string = (type == 'hatchingPotions') ? window.env.t('hatchingPotion') : (type == 'eggs') ? window.env.t('eggSingular') : (type == 'quests') ? window.env.t('quest') : (item.key == 'Saddle') ? window.env.t('foodSaddleText').toLowerCase() : (type == 'special') ? item.key : type; // this is ugly but temporary, once the purchase modal is done this will be removed
|
if (type == 'weapon' || type == 'armor' || type == 'head' || type == 'shield') {
|
||||||
var message = window.env.t('buyThis', {text: string, price: item.value, gems: gems})
|
if (gems < ((item.specialClass == "wizard") && (item.type == "weapon")) + 1) return $rootScope.openModal('buyGems');
|
||||||
|
var message = window.env.t('buyThis', {text: string, price: ((item.specialClass == "wizard") && (item.type == "weapon")) + 1, gems: gems})
|
||||||
|
if($window.confirm(message))
|
||||||
|
User.user.ops.purchase({params:{type:"gear",key:item.key}});
|
||||||
|
} else {
|
||||||
|
if(gems < item.value) return $rootScope.openModal('buyGems');
|
||||||
|
var message = window.env.t('buyThis', {text: string, price: item.value, gems: gems})
|
||||||
|
if($window.confirm(message))
|
||||||
|
User.user.ops.purchase({params:{type:type,key:item.key}});
|
||||||
|
}
|
||||||
|
|
||||||
if($window.confirm(message))
|
|
||||||
User.user.ops.purchase({params:{type:type,key:item.key}});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.choosePet = function(egg, potion){
|
$scope.choosePet = function(egg, potion){
|
||||||
|
|
@ -157,5 +164,23 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User',
|
||||||
$rootScope.selectedQuest = item;
|
$rootScope.selectedQuest = item;
|
||||||
$rootScope.openModal('buyQuest', {controller:'InventoryCtrl'});
|
$rootScope.openModal('buyQuest', {controller:'InventoryCtrl'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.getSeasonalShopArray = function(set){
|
||||||
|
var flatGearArray = _.toArray(Content.gear.flat);
|
||||||
|
|
||||||
|
var filteredArray = _.where(flatGearArray, {index: set});
|
||||||
|
|
||||||
|
return filteredArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.getSeasonalShopQuests = function(set){
|
||||||
|
var questArray = _.toArray(Content.quests);
|
||||||
|
|
||||||
|
var filteredArray = _.filter(questArray, function(q){
|
||||||
|
return q.key == "evilsanta" || q.key == "evilsanta2";
|
||||||
|
});
|
||||||
|
|
||||||
|
return filteredArray;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,9 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
|
||||||
$scope.castStart = function(spell) {
|
$scope.castStart = function(spell) {
|
||||||
if (User.user.stats.mp < spell.mana) return Notification.text(window.env.t('notEnoughMana'));
|
if (User.user.stats.mp < spell.mana) return Notification.text(window.env.t('notEnoughMana'));
|
||||||
|
|
||||||
|
if (spell.key == 'nye' && User.user.stats.gp < spell.value)
|
||||||
|
return Notification.text('Not enough gold.');
|
||||||
|
|
||||||
$rootScope.applyingAction = true;
|
$rootScope.applyingAction = true;
|
||||||
$scope.spell = spell;
|
$scope.spell = spell;
|
||||||
if (spell.target == 'self') {
|
if (spell.target == 'self') {
|
||||||
|
|
@ -236,7 +239,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
|
||||||
$scope.spell = null;
|
$scope.spell = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Becuase our angular-ui-router uses anchors for urls (/#/options/groups/party), window.location.href=... won't
|
// Because our angular-ui-router uses anchors for urls (/#/options/groups/party), window.location.href=... won't
|
||||||
// reload the page. Perform manually.
|
// reload the page. Perform manually.
|
||||||
$rootScope.hardRedirect = function(url){
|
$rootScope.hardRedirect = function(url){
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,8 @@ habitrpg.controller('SettingsCtrl',
|
||||||
User.set({'preferences.language': $scope.language.code});
|
User.set({'preferences.language': $scope.language.code});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.availableFormats = ['MM/dd/yyyy','dd/MM/yyyy', 'yyyy/MM/dd'];
|
||||||
|
|
||||||
$scope.reroll = function(){
|
$scope.reroll = function(){
|
||||||
User.user.ops.reroll({});
|
User.user.ops.reroll({});
|
||||||
$rootScope.$state.go('tasks');
|
$rootScope.$state.go('tasks');
|
||||||
|
|
@ -179,5 +181,15 @@ habitrpg.controller('SettingsCtrl',
|
||||||
$scope.deleteWebhook = function(id) {
|
$scope.deleteWebhook = function(id) {
|
||||||
User.user.ops.deleteWebhook({params:{id:id}});
|
User.user.ops.deleteWebhook({params:{id:id}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.applyCoupon = function(coupon){
|
||||||
|
$http.get(ApiUrlService.get() + '/api/v2/coupons/valid-discount/'+coupon)
|
||||||
|
.success(function(){
|
||||||
|
Notification.text("Coupon applied!");
|
||||||
|
var subs = $scope.Content.subscriptionBlocks;
|
||||||
|
subs["basic_6mo"].discount = true;
|
||||||
|
subs["google_6mo"].discount = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
|
||||||
*/
|
*/
|
||||||
function focusChecklist(task,index) {
|
function focusChecklist(task,index) {
|
||||||
window.setTimeout(function(){
|
window.setTimeout(function(){
|
||||||
$('#task-'+task.id+' .checklist-form .inline-edit')[index].focus();
|
$('#task-'+task.id+' .checklist-form input[type="text"]')[index].focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$scope.addChecklist = function(task) {
|
$scope.addChecklist = function(task) {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ angular.module('groupServices', ['ngResource']).
|
||||||
//query: {method: "GET", isArray:false},
|
//query: {method: "GET", isArray:false},
|
||||||
postChat: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/chat'},
|
postChat: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/chat'},
|
||||||
deleteChatMessage: {method: "DELETE", url: ApiUrlService.get() + '/api/v2/groups/:gid/chat/:messageId'},
|
deleteChatMessage: {method: "DELETE", url: ApiUrlService.get() + '/api/v2/groups/:gid/chat/:messageId'},
|
||||||
|
flagChatMessage: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/chat/:messageId/flag'},
|
||||||
|
clearFlagCount: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/chat/:messageId/clearflags'},
|
||||||
join: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/join'},
|
join: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/join'},
|
||||||
leave: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/leave'},
|
leave: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/leave'},
|
||||||
invite: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/invite'},
|
invite: {method: "POST", url: ApiUrlService.get() + '/api/v2/groups/:gid/invite'},
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,12 @@
|
||||||
angular.module('paymentServices',[]).factory('Payments',
|
angular.module('paymentServices',[]).factory('Payments',
|
||||||
['$rootScope', 'User', '$http', 'Content',
|
['$rootScope', 'User', '$http', 'Content',
|
||||||
function($rootScope, User, $http, Content) {
|
function($rootScope, User, $http, Content) {
|
||||||
var user = User.user;
|
|
||||||
var plan = User.user.purchased.plan;
|
|
||||||
var Payments = {};
|
var Payments = {};
|
||||||
|
|
||||||
Payments.currentSub = _.find(Content.subscriptionBlocks, function(b){
|
|
||||||
switch (plan.paymentMethod) {
|
|
||||||
case 'Stripe':
|
|
||||||
case 'Paypal': // FIXME store paypalKey somewhere?
|
|
||||||
return b.key == plan.planId;
|
|
||||||
default: return undefined;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Payments.showStripe = function(data) {
|
Payments.showStripe = function(data) {
|
||||||
var sub =
|
var sub =
|
||||||
data.subscription ? data.subscription
|
data.subscription ? data.subscription
|
||||||
: data.gift && data.gift.type=='subscription' ? data.gift.subscription.months
|
: data.gift && data.gift.type=='subscription' ? data.gift.subscription.key
|
||||||
: false;
|
: false;
|
||||||
sub = sub && Content.subscriptionBlocks[sub];
|
sub = sub && Content.subscriptionBlocks[sub];
|
||||||
var amount = // 500 = $5
|
var amount = // 500 = $5
|
||||||
|
|
@ -36,7 +25,8 @@ function($rootScope, User, $http, Content) {
|
||||||
token: function(res) {
|
token: function(res) {
|
||||||
var url = '/stripe/checkout?a=a'; // just so I can concat &x=x below
|
var url = '/stripe/checkout?a=a'; // just so I can concat &x=x below
|
||||||
if (data.gift) url += '&gift=' + Payments.encodeGift(data.uuid, data.gift);
|
if (data.gift) url += '&gift=' + Payments.encodeGift(data.uuid, data.gift);
|
||||||
if (data.subscription) url += '&sub='+sub.months;
|
if (data.subscription) url += '&sub='+sub.key;
|
||||||
|
if (data.coupon) url += '&coupon='+data.coupon;
|
||||||
$http.post(url, res).success(function() {
|
$http.post(url, res).success(function() {
|
||||||
window.location.reload(true);
|
window.location.reload(true);
|
||||||
}).error(function(res) {
|
}).error(function(res) {
|
||||||
|
|
@ -50,9 +40,9 @@ function($rootScope, User, $http, Content) {
|
||||||
StripeCheckout.open({
|
StripeCheckout.open({
|
||||||
key: window.env.STRIPE_PUB_KEY,
|
key: window.env.STRIPE_PUB_KEY,
|
||||||
address: false,
|
address: false,
|
||||||
name: 'Update',
|
name: window.env.t('subUpdateTitle'),
|
||||||
description: 'Update the card to be charged for your subscription',
|
description: window.env.t('subUpdateDescription'),
|
||||||
panelLabel: 'Update Card',
|
panelLabel: window.env.t('subUpdateCard'),
|
||||||
token: function(data) {
|
token: function(data) {
|
||||||
var url = '/stripe/subscribe/edit';
|
var url = '/stripe/subscribe/edit';
|
||||||
$http.post(url, data).success(function() {
|
$http.post(url, data).success(function() {
|
||||||
|
|
@ -66,7 +56,7 @@ function($rootScope, User, $http, Content) {
|
||||||
|
|
||||||
Payments.cancelSubscription = function(){
|
Payments.cancelSubscription = function(){
|
||||||
if (!confirm(window.env.t('sureCancelSub'))) return;
|
if (!confirm(window.env.t('sureCancelSub'))) return;
|
||||||
window.location.href = '/' + plan.paymentMethod.toLowerCase() + '/subscribe/cancel?_id=' + user._id + '&apiToken=' + user.apiToken;
|
window.location.href = '/' + User.user.purchased.plan.paymentMethod.toLowerCase() + '/subscribe/cancel?_id=' + User.user._id + '&apiToken=' + User.user.apiToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
Payments.encodeGift = function(uuid, gift){
|
Payments.encodeGift = function(uuid, gift){
|
||||||
|
|
|
||||||
|
|
@ -148,21 +148,19 @@ api.loginSocial = function(req, res, next) {
|
||||||
network = req.body.network;
|
network = req.body.network;
|
||||||
if (network!=='facebook')
|
if (network!=='facebook')
|
||||||
return res.json(401, {err:"Only Facebook supported currently."});
|
return res.json(401, {err:"Only Facebook supported currently."});
|
||||||
async.waterfall([
|
async.auto({
|
||||||
function(cb){
|
profile: function (cb) {
|
||||||
passport._strategies[network].userProfile(access_token, cb);
|
passport._strategies[network].userProfile(access_token, cb);
|
||||||
},
|
},
|
||||||
function(profile, cb) {
|
user: ['profile', function (cb, results) {
|
||||||
var q = {};q['auth.'+network+'.id'] = profile.id;
|
var q = {};
|
||||||
User.findOne(q, {_id:1, apiToken:1, auth:1}, function(err, user){
|
q['auth.' + network + '.id'] = results.profile.id;
|
||||||
if (err) return cb(err);
|
User.findOne(q, {_id: 1, apiToken: 1, auth: 1}, cb);
|
||||||
cb(null, {user:user, profile:profile});
|
}],
|
||||||
});
|
register: ['profile', 'user', function (cb, results) {
|
||||||
},
|
if (results.user) return cb(null, results.user);
|
||||||
function(data, cb){
|
|
||||||
if (data.user) return cb(null, data.user);
|
|
||||||
// Create new user
|
// Create new user
|
||||||
var prof = data.profile;
|
var prof = results.profile;
|
||||||
var user = {
|
var user = {
|
||||||
preferences: {
|
preferences: {
|
||||||
language: req.language // User language detected from browser, not saved
|
language: req.language // User language detected from browser, not saved
|
||||||
|
|
@ -175,15 +173,16 @@ api.loginSocial = function(req, res, next) {
|
||||||
user = new User(user);
|
user = new User(user);
|
||||||
user.save(cb);
|
user.save(cb);
|
||||||
|
|
||||||
if(isProd && prof.emails && prof.emails[0] && prof.emails[0].value){
|
if (isProd && prof.emails && prof.emails[0] && prof.emails[0].value) {
|
||||||
utils.txnEmail({name:prof.displayName || prof.username, email:prof.emails[0].value}, 'welcome');
|
utils.txnEmail({name: prof.displayName || prof.username, email: prof.emails[0].value}, 'welcome');
|
||||||
}
|
}
|
||||||
ga.event('register', network).send();
|
ga.event('register', network).send();
|
||||||
}
|
}]
|
||||||
], function(err, user){
|
}, function(err, results){
|
||||||
if (err) return res.json(401, {err: err.toString ? err.toString() : err});
|
if (err) return res.json(401, {err: err.toString ? err.toString() : err});
|
||||||
if (user.auth.blocked) return res.json(401, accountSuspended(user._id));
|
var acct = results.register[0] ? results.register[0] : results.register;
|
||||||
return res.json(200, {id: user.id, token:user.apiToken});
|
if (acct.auth.blocked) return res.json(401, accountSuspended(acct._id));
|
||||||
|
return res.json(200, {id:acct._id, token:acct.apiToken});
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -206,16 +205,15 @@ api.resetPassword = function(req, res, next){
|
||||||
if (!user) return res.send(500, {err:"Couldn't find a user registered for email " + email});
|
if (!user) return res.send(500, {err:"Couldn't find a user registered for email " + email});
|
||||||
user.auth.local.salt = salt;
|
user.auth.local.salt = salt;
|
||||||
user.auth.local.hashed_password = hashed_password;
|
user.auth.local.hashed_password = hashed_password;
|
||||||
utils.sendEmail({
|
utils.txnEmail(user, 'reset-password', [
|
||||||
from: "HabitRPG <admin@habitrpg.com>",
|
{name: "NEW_PASSWORD", content: newPassword},
|
||||||
to: email,
|
{name: "USERNAME", content: user.auth.local.username}
|
||||||
subject: "Password Reset for HabitRPG",
|
]);
|
||||||
text: "Password for " + user.auth.local.username + " has been reset to " + newPassword + ". Log in at " + nconf.get('BASE_URL'),
|
user.save(function(err){
|
||||||
html: "Password for <strong>" + user.auth.local.username + "</strong> has been reset to <strong>" + newPassword + "</strong>. Log in at " + nconf.get('BASE_URL')
|
if(err) return next(err);
|
||||||
|
res.send('New password sent to '+ email);
|
||||||
|
email = salt = newPassword = hashed_password = null;
|
||||||
});
|
});
|
||||||
user.save();
|
|
||||||
res.send('New password sent to '+ email);
|
|
||||||
email = salt = newPassword = hashed_password = null;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -147,69 +147,63 @@ api.getMember = function(req, res, next) {
|
||||||
// CREATE
|
// CREATE
|
||||||
api.create = function(req, res, next){
|
api.create = function(req, res, next){
|
||||||
var user = res.locals.user;
|
var user = res.locals.user;
|
||||||
var group, chal;
|
|
||||||
|
|
||||||
// First, make sure they've selected a legit group, and store it for later
|
async.auto({
|
||||||
var waterfall = [
|
get_group: function(cb){
|
||||||
function(cb){
|
var q = {_id:req.body.group};
|
||||||
Group.findById(req.body.group).exec(cb);
|
if (req.body.group!='habitrpg') q.members = {$in:[user._id]}; // make sure they're a member of the group
|
||||||
|
Group.findOne(q, cb);
|
||||||
},
|
},
|
||||||
function(_group, cb){
|
save_chal: ['get_group', function(cb, results){
|
||||||
if (!_group) return cb("Group." + req.body.group + " not found");
|
var group = results.get_group,
|
||||||
group = _group;
|
prize = +req.body.prize;
|
||||||
cb(null);
|
if (!group)
|
||||||
}
|
return cb({code:404, err:"Group." + req.body.group + " not found"});
|
||||||
];
|
if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id)
|
||||||
|
return cb({code:401, err: "Only the group leader can create challenges"});
|
||||||
|
// If they're adding a prize, do some validation
|
||||||
|
if (prize < 0)
|
||||||
|
return cb({code:401, err: 'Challenge prize must be >= 0'});
|
||||||
|
if (req.body.group=='habitrpg' && prize < 1)
|
||||||
|
return cb({code:401, err: 'Prize must be at least 1 Gem for public challenges.'});
|
||||||
|
if (prize > 0) {
|
||||||
|
var groupBalance = ((group.balance && group.leader==user._id) ? group.balance : 0);
|
||||||
|
var prizeCost = prize/4; // I really should have stored user.balance as gems rather than dollars... stupid...
|
||||||
|
if (prizeCost > user.balance + groupBalance)
|
||||||
|
return cb("You can't afford this prize. Purchase more gems or lower the prize amount.")
|
||||||
|
|
||||||
// If they're adding a prize, do some validation
|
if (groupBalance >= prizeCost) {
|
||||||
if (+req.body.prize < 0) return res.json(401, {err: 'Challenge prize must be >= 0'});
|
// Group pays for all of prize
|
||||||
if (req.body.group=='habitrpg' && +req.body.prize < 1) return res.json(401, {err: 'Prize must be at least 1 Gem for public challenges.'});
|
group.balance -= prizeCost;
|
||||||
if (+req.body.prize > 0) {
|
} else if (groupBalance > 0) {
|
||||||
waterfall.push(function(cb){
|
// User pays remainder of prize cost after group
|
||||||
var groupBalance = ((group.balance && group.leader==user._id) ? group.balance : 0);
|
var remainder = prizeCost - group.balance;
|
||||||
var prizeCost = req.body.prize/4; // I really should have stored user.balance as gems rather than dollars... stupid...
|
group.balance = 0;
|
||||||
if (prizeCost > user.balance + groupBalance)
|
user.balance -= remainder;
|
||||||
return cb("You can't afford this prize. Purchase more gems or lower the prize amount.")
|
} else {
|
||||||
|
// User pays for all of prize
|
||||||
if (groupBalance >= prizeCost) {
|
user.balance -= prizeCost;
|
||||||
// Group pays for all of prize
|
}
|
||||||
group.balance -= prizeCost;
|
}
|
||||||
} else if (groupBalance > 0) {
|
|
||||||
// User pays remainder of prize cost after group
|
|
||||||
var remainder = prizeCost - group.balance;
|
|
||||||
group.balance = 0;
|
|
||||||
user.balance -= remainder;
|
|
||||||
} else {
|
|
||||||
// User pays for all of prize
|
|
||||||
user.balance -= prizeCost;
|
|
||||||
}
|
|
||||||
cb(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
waterfall = waterfall.concat([
|
|
||||||
function(cb) { // if we're dealing with prize above, arguemnts will be `group, numRows, cb` - else `cb`
|
|
||||||
req.body.leader = user._id;
|
req.body.leader = user._id;
|
||||||
req.body.official = user.contributor.admin && req.body.official;
|
req.body.official = user.contributor.admin && req.body.official;
|
||||||
var chal = new Challenge(req.body); // FIXME sanitize
|
var chal = new Challenge(req.body); // FIXME sanitize
|
||||||
chal.members.push(user._id);
|
chal.members.push(user._id);
|
||||||
chal.save(cb)
|
chal.save(cb);
|
||||||
},
|
}],
|
||||||
function(_chal, num, cb){
|
save_group: ['save_chal', function(cb, results){
|
||||||
chal = _chal;
|
results.get_group.challenges.push(results.save_chal[0]._id);
|
||||||
group.challenges.push(chal._id);
|
results.get_group.save(cb);
|
||||||
group.save(cb);
|
}],
|
||||||
},
|
sync_user: ['save_group', function(cb, results){
|
||||||
function(_group, num, cb) {
|
|
||||||
// Auto-join creator to challenge (see members.push above)
|
// Auto-join creator to challenge (see members.push above)
|
||||||
chal.syncToUser(user, cb);
|
results.save_chal[0].syncToUser(user, cb);
|
||||||
}
|
}]
|
||||||
]);
|
}, function(err, results){
|
||||||
async.waterfall(waterfall, function(err){
|
if (err) return err.code? res.json(err.code, err) : next(err);
|
||||||
if (err) return next(err);
|
return res.json(results.save_chal[0]);
|
||||||
res.json(chal);
|
user = null;
|
||||||
user = group = chal = null;
|
})
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UPDATE
|
// UPDATE
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ function clone(a) {
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
var utils = require('./../utils');
|
||||||
var shared = require('habitrpg-shared');
|
var shared = require('habitrpg-shared');
|
||||||
var User = require('./../models/user').model;
|
var User = require('./../models/user').model;
|
||||||
var Group = require('./../models/group').model;
|
var Group = require('./../models/group').model;
|
||||||
var Challenge = require('./../models/challenge').model;
|
var Challenge = require('./../models/challenge').model;
|
||||||
|
var isProd = nconf.get('NODE_ENV') === 'production';
|
||||||
var api = module.exports;
|
var api = module.exports;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -187,7 +189,7 @@ api.update = function(req, res, next) {
|
||||||
if(group.leader !== user._id)
|
if(group.leader !== user._id)
|
||||||
return res.json(401, {err: "Only the group leader can update the group!"});
|
return res.json(401, {err: "Only the group leader can update the group!"});
|
||||||
|
|
||||||
'name description logo logo leaderMessage leader'.split(' ').forEach(function(attr){
|
'name description logo logo leaderMessage leader leaderOnly'.split(' ').forEach(function(attr){
|
||||||
group[attr] = req.body[attr];
|
group[attr] = req.body[attr];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -256,6 +258,82 @@ api.deleteChatMessage = function(req, res, next){
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api.flagChatMessage = function(req, res, next){
|
||||||
|
var user = res.locals.user
|
||||||
|
var group = res.locals.group;
|
||||||
|
var message = _.find(group.chat, {id: req.params.mid});
|
||||||
|
|
||||||
|
if(!message) return res.json(404, {err: "Message not found!"});
|
||||||
|
if(message.uuid == user._id) return res.json(401, {err: "Can't report your own message."});
|
||||||
|
|
||||||
|
User.findOne({_id: message.uuid}, {auth: 1}, function(err, author){
|
||||||
|
if(err) return next(err);
|
||||||
|
|
||||||
|
// Log user ids that have flagged the message
|
||||||
|
if(!message.flags) message.flags = {};
|
||||||
|
if(message.flags[user._id] && !user.contributor.admin) return res.json(401, {err: "You have already reported this message"});
|
||||||
|
message.flags[user._id] = true;
|
||||||
|
|
||||||
|
// Log total number of flags (publicly viewable)
|
||||||
|
if(!message.flagCount) message.flagCount = 0;
|
||||||
|
if(user.contributor.admin){
|
||||||
|
// Arbitraty amount, higher than 2
|
||||||
|
message.flagCount = 5;
|
||||||
|
} else {
|
||||||
|
message.flagCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
group.markModified('chat');
|
||||||
|
group.save(function(err,_saved){
|
||||||
|
if(err) return next(err);
|
||||||
|
if (isProd){
|
||||||
|
utils.txnEmail({email: nconf.get('FLAG_REPORT_EMAIL')}, 'flag-report-to-mods', [
|
||||||
|
{name: "MESSAGE_TIME", content: (new Date(message.timestamp)).toString()},
|
||||||
|
{name: "MESSAGE_TEXT", content: message.text},
|
||||||
|
|
||||||
|
{name: "REPORTER_USERNAME", content: user.profile.name},
|
||||||
|
{name: "REPORTER_UUID", content: user._id},
|
||||||
|
{name: "REPORTER_EMAIL", content: user.auth.local ? user.auth.local.email : ((user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0]) ? user.auth.facebook.emails[0].value : null)},
|
||||||
|
{name: "REPORTER_MODAL_URL", content: "https://habitrpg.com/static/front/#?memberId=" + user._id},
|
||||||
|
|
||||||
|
{name: "AUTHOR_USERNAME", content: message.user},
|
||||||
|
{name: "AUTHOR_UUID", content: message.uuid},
|
||||||
|
{name: "AUTHOR_EMAIL", content: author.auth.local ? author.auth.local.email : ((author.auth.facebook && author.auth.facebook.emails && author.auth.facebook.emails[0]) ? author.auth.facebook.emails[0].value : null)},
|
||||||
|
{name: "AUTHOR_MODAL_URL", content: "https://habitrpg.com/static/front/#?memberId=" + message.uuid},
|
||||||
|
|
||||||
|
{name: "GROUP_NAME", content: group.name},
|
||||||
|
{name: "GROUP_TYPE", content: group.type},
|
||||||
|
{name: "GROUP_ID", content: group._id},
|
||||||
|
{name: "GROUP_URL", content: group._id == 'habitrpg' ? (nconf.get('BASE_URL') + '/#/options/groups/tavern') : (group.type === 'guild' ? (nconf.get('BASE_URL')+ '/#/options/groups/guilds/' + group._id) : 'party')},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return res.send(204);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
api.clearFlagCount = function(req, res, next){
|
||||||
|
var user = res.locals.user
|
||||||
|
var group = res.locals.group;
|
||||||
|
var message = _.find(group.chat, {id: req.params.mid});
|
||||||
|
|
||||||
|
if(!message) return res.json(404, {err: "Message not found!"});
|
||||||
|
|
||||||
|
if(user.contributor.admin){
|
||||||
|
message.flagCount = 0;
|
||||||
|
|
||||||
|
group.markModified('chat');
|
||||||
|
group.save(function(err,_saved){
|
||||||
|
if(err) return next(err);
|
||||||
|
return res.send(204);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
return res.json(401, {err: "Only an admin can clear the flag count!"})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
api.seenMessage = function(req,res,next){
|
api.seenMessage = function(req,res,next){
|
||||||
// Skip the auth step, we want this to be fast. If !found with uuid/token, then it just doesn't save
|
// Skip the auth step, we want this to be fast. If !found with uuid/token, then it just doesn't save
|
||||||
// Check for req.params.gid to exist
|
// Check for req.params.gid to exist
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ api.sendMessage = function(user, member, data){
|
||||||
msg = data.message
|
msg = data.message
|
||||||
} else {
|
} else {
|
||||||
msg = "`Hello " + member.profile.name + ", " + user.profile.name + " has sent you ";
|
msg = "`Hello " + member.profile.name + ", " + user.profile.name + " has sent you ";
|
||||||
msg += (data.type=='gems') ? data.gems.amount + " gems!`" : data.subscription.months + " months of subscription!`";
|
msg += (data.type=='gems') ? data.gems.amount + " gems!`" : shared.content.subscriptionBlocks[data.subscription.key].months + " months of subscription!`";
|
||||||
msg += data.message;
|
msg += data.message;
|
||||||
}
|
}
|
||||||
shared.refPush(member.inbox.messages, groups.chatDefaults(msg, user));
|
shared.refPush(member.inbox.messages, groups.chatDefaults(msg, user));
|
||||||
|
|
|
||||||
129
src/controllers/payments/iap.js
Normal file
129
src/controllers/payments/iap.js
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
var iap = require('in-app-purchase');
|
||||||
|
var async = require('async');
|
||||||
|
var payments = require('./index');
|
||||||
|
var nconf = require('nconf');
|
||||||
|
|
||||||
|
var inAppPurchase = require('in-app-purchase');
|
||||||
|
inAppPurchase.config({
|
||||||
|
// this is the path to the directory containing iap-sanbox/iap-live files
|
||||||
|
googlePublicKeyPath: nconf.get("IAP_GOOGLE_KEYDIR")
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validation ERROR Codes
|
||||||
|
var INVALID_PAYLOAD = 6778001;
|
||||||
|
var CONNECTION_FAILED = 6778002;
|
||||||
|
var PURCHASE_EXPIRED = 6778003;
|
||||||
|
|
||||||
|
exports.androidVerify = function(req, res, next) {
|
||||||
|
var iapBody = req.body;
|
||||||
|
var user = res.locals.user;
|
||||||
|
|
||||||
|
iap.setup(function (error) {
|
||||||
|
if (error) {
|
||||||
|
var resObj = {
|
||||||
|
ok: false,
|
||||||
|
data: 'IAP Error'
|
||||||
|
};
|
||||||
|
|
||||||
|
console.error('IAP Setup ERROR');
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
res.json(resObj);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
google receipt must be provided as an object
|
||||||
|
{
|
||||||
|
"data": "{stringified data object}",
|
||||||
|
"signature": "signature from google"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
var testObj = {
|
||||||
|
data: iapBody.transaction.receipt,
|
||||||
|
signature: iapBody.transaction.signature
|
||||||
|
};
|
||||||
|
|
||||||
|
// iap is ready
|
||||||
|
iap.validate(iap.GOOGLE, testObj, function (err, googleRes) {
|
||||||
|
if (err) {
|
||||||
|
var resObj = {
|
||||||
|
ok: false,
|
||||||
|
data: {
|
||||||
|
code: INVALID_PAYLOAD,
|
||||||
|
message: err.toString()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(resObj);
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iap.isValidated(googleRes)) {
|
||||||
|
var resObj = {
|
||||||
|
ok: true,
|
||||||
|
data: googleRes
|
||||||
|
};
|
||||||
|
|
||||||
|
payments.buyGems({user:user, paymentMethod:'IAP GooglePlay'});
|
||||||
|
|
||||||
|
// yay good!
|
||||||
|
res.json(resObj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.iosVerify = function(req, res, next) {
|
||||||
|
console.info(req.body);
|
||||||
|
|
||||||
|
var iapBody = req.body;
|
||||||
|
var user = res.locals.user;
|
||||||
|
|
||||||
|
iap.setup(function (error) {
|
||||||
|
if (error) {
|
||||||
|
var resObj = {
|
||||||
|
ok: false,
|
||||||
|
data: 'IAP Error'
|
||||||
|
};
|
||||||
|
|
||||||
|
console.error('IAP Setup ERROR');
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
res.json(resObj);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iap is ready
|
||||||
|
iap.validate(iap.APPLE, iapBody.transaction.receipt, function (err, appleRes) {
|
||||||
|
if (err) {
|
||||||
|
var resObj = {
|
||||||
|
ok: false,
|
||||||
|
data: {
|
||||||
|
code: INVALID_PAYLOAD,
|
||||||
|
message: err.toString()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(resObj);
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iap.isValidated(appleRes)) {
|
||||||
|
var resObj = {
|
||||||
|
ok: true,
|
||||||
|
data: appleRes
|
||||||
|
};
|
||||||
|
|
||||||
|
payments.buyGems({user:user, paymentMethod:'IAP AppleStore'});
|
||||||
|
|
||||||
|
// yay good!
|
||||||
|
res.json(resObj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -7,9 +7,11 @@ var moment = require('moment');
|
||||||
var isProduction = nconf.get("NODE_ENV") === "production";
|
var isProduction = nconf.get("NODE_ENV") === "production";
|
||||||
var stripe = require('./stripe');
|
var stripe = require('./stripe');
|
||||||
var paypal = require('./paypal');
|
var paypal = require('./paypal');
|
||||||
var User = require('mongoose').model('User');
|
|
||||||
var members = require('../members')
|
var members = require('../members')
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
var iap = require('./iap');
|
||||||
|
var mongoose= require('mongoose');
|
||||||
|
var cc = require('coupon-code');
|
||||||
|
|
||||||
function revealMysteryItems(user) {
|
function revealMysteryItems(user) {
|
||||||
_.each(shared.content.gear.flat, function(item) {
|
_.each(shared.content.gear.flat, function(item) {
|
||||||
|
|
@ -29,8 +31,8 @@ exports.createSubscription = function(data, cb) {
|
||||||
var recipient = data.gift ? data.gift.member : data.user;
|
var recipient = data.gift ? data.gift.member : data.user;
|
||||||
//if (!recipient.purchased.plan) recipient.purchased.plan = {}; // FIXME double-check, this should never be the case
|
//if (!recipient.purchased.plan) recipient.purchased.plan = {}; // FIXME double-check, this should never be the case
|
||||||
var p = recipient.purchased.plan;
|
var p = recipient.purchased.plan;
|
||||||
var months = +(data.gift ? data.gift.subscription.months : data.sub.months);
|
var block = shared.content.subscriptionBlocks[data.gift ? data.gift.subscription.key : data.sub.key];
|
||||||
var block = shared.content.subscriptionBlocks[months];
|
var months = +block.months;
|
||||||
|
|
||||||
if (data.gift) {
|
if (data.gift) {
|
||||||
if (p.customerId && !p.dateTerminated) { // User has active plan
|
if (p.customerId && !p.dateTerminated) { // User has active plan
|
||||||
|
|
@ -114,6 +116,14 @@ exports.buyGems = function(data, cb) {
|
||||||
], cb);
|
], cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.validCoupon = function(req, res, next){
|
||||||
|
mongoose.model('Coupon').findOne({_id:cc.validate(req.params.code), event:'google_6mo'}, function(err, coupon){
|
||||||
|
if (err) return next(err);
|
||||||
|
if (!coupon) return res.json(401, {err:"Invalid coupon code"});
|
||||||
|
return res.send(200);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
exports.stripeCheckout = stripe.checkout;
|
exports.stripeCheckout = stripe.checkout;
|
||||||
exports.stripeSubscribeCancel = stripe.subscribeCancel;
|
exports.stripeSubscribeCancel = stripe.subscribeCancel;
|
||||||
exports.stripeSubscribeEdit = stripe.subscribeEdit;
|
exports.stripeSubscribeEdit = stripe.subscribeEdit;
|
||||||
|
|
@ -123,4 +133,7 @@ exports.paypalSubscribeSuccess = paypal.executeBillingAgreement;
|
||||||
exports.paypalSubscribeCancel = paypal.cancelSubscription;
|
exports.paypalSubscribeCancel = paypal.cancelSubscription;
|
||||||
exports.paypalCheckout = paypal.createPayment;
|
exports.paypalCheckout = paypal.createPayment;
|
||||||
exports.paypalCheckoutSuccess = paypal.executePayment;
|
exports.paypalCheckoutSuccess = paypal.executePayment;
|
||||||
exports.paypalIPN = paypal.ipn;
|
exports.paypalIPN = paypal.ipn;
|
||||||
|
|
||||||
|
exports.iapAndroidVerify = iap.androidVerify;
|
||||||
|
exports.iapIosVerify = iap.iosVerify;
|
||||||
|
|
@ -9,12 +9,14 @@ var logger = require('../../logging');
|
||||||
var ipn = require('paypal-ipn');
|
var ipn = require('paypal-ipn');
|
||||||
var paypal = require('paypal-rest-sdk');
|
var paypal = require('paypal-rest-sdk');
|
||||||
var shared = require('habitrpg-shared');
|
var shared = require('habitrpg-shared');
|
||||||
|
var mongoose = require('mongoose');
|
||||||
|
var cc = require('coupon-code');
|
||||||
|
|
||||||
// This is the plan.id for paypal subscriptions. You have to set up billing plans via their REST sdk (they don't have
|
// This is the plan.id for paypal subscriptions. You have to set up billing plans via their REST sdk (they don't have
|
||||||
// a web interface for billing-plan creation), see ./paypalBillingSetup.js for how. After the billing plan is created
|
// a web interface for billing-plan creation), see ./paypalBillingSetup.js for how. After the billing plan is created
|
||||||
// there, get it's plan.id and store it in config.json
|
// there, get it's plan.id and store it in config.json
|
||||||
_.each(shared.content.subscriptionBlocks, function(block){
|
_.each(shared.content.subscriptionBlocks, function(block){
|
||||||
block.paypalKey = nconf.get("PAYPAL:billing_plans:"+block.months);
|
block.paypalKey = nconf.get("PAYPAL:billing_plans:"+block.key);
|
||||||
});
|
});
|
||||||
|
|
||||||
paypal.configure({
|
paypal.configure({
|
||||||
|
|
@ -30,23 +32,33 @@ var parseErr = function(res, err){
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createBillingAgreement = function(req,res,next){
|
exports.createBillingAgreement = function(req,res,next){
|
||||||
req.session.paypalBlock = req.query.sub;
|
var sub = shared.content.subscriptionBlocks[req.query.sub];
|
||||||
var block = shared.content.subscriptionBlocks[req.query.sub];
|
async.waterfall([
|
||||||
var billingPlanTitle = "HabitRPG Subscription" + ' ($'+block.price+' every '+block.months+' months, recurring)';
|
function(cb){
|
||||||
var billingAgreementAttributes = {
|
if (!sub.discount) return cb(null, null);
|
||||||
"name": billingPlanTitle,
|
if (!req.query.coupon) return cb('Please provide a coupon code for this plan.');
|
||||||
"description": billingPlanTitle,
|
mongoose.model('Coupon').findOne({_id:cc.validate(req.query.coupon), event:sub.key}, cb);
|
||||||
"start_date": moment().add({minutes:5}).format(),
|
|
||||||
"plan": {
|
|
||||||
"id": block.paypalKey
|
|
||||||
},
|
},
|
||||||
"payer": {
|
function(coupon, cb){
|
||||||
"payment_method": "paypal"
|
if (sub.discount && !coupon) return cb('Invalid coupon code.');
|
||||||
|
var billingPlanTitle = "HabitRPG Subscription" + ' ($'+sub.price+' every '+sub.months+' months, recurring)';
|
||||||
|
var billingAgreementAttributes = {
|
||||||
|
"name": billingPlanTitle,
|
||||||
|
"description": billingPlanTitle,
|
||||||
|
"start_date": moment().add({minutes:5}).format(),
|
||||||
|
"plan": {
|
||||||
|
"id": sub.paypalKey
|
||||||
|
},
|
||||||
|
"payer": {
|
||||||
|
"payment_method": "paypal"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
paypal.billingAgreement.create(billingAgreementAttributes, cb);
|
||||||
}
|
}
|
||||||
};
|
], function(err, billingAgreement){
|
||||||
paypal.billingAgreement.create(billingAgreementAttributes, function (err, billingAgreement) {
|
|
||||||
if (err) return parseErr(res, err);
|
if (err) return parseErr(res, err);
|
||||||
// For approving subscription via Paypal, first redirect user to: approval_url
|
// For approving subscription via Paypal, first redirect user to: approval_url
|
||||||
|
req.session.paypalBlock = req.query.sub;
|
||||||
var approval_url = _.find(billingAgreement.links, {rel:'approval_url'}).href;
|
var approval_url = _.find(billingAgreement.links, {rel:'approval_url'}).href;
|
||||||
res.redirect(approval_url);
|
res.redirect(approval_url);
|
||||||
});
|
});
|
||||||
|
|
@ -82,10 +94,10 @@ exports.createPayment = function(req, res) {
|
||||||
var gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
|
var gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
|
||||||
var price = !gift ? 5.00
|
var price = !gift ? 5.00
|
||||||
: gift.type=='gems' ? Number(gift.gems.amount/4).toFixed(2)
|
: gift.type=='gems' ? Number(gift.gems.amount/4).toFixed(2)
|
||||||
: Number(shared.content.subscriptionBlocks[gift.subscription.months].price).toFixed(2);
|
: Number(shared.content.subscriptionBlocks[gift.subscription.key].price).toFixed(2);
|
||||||
var description = !gift ? "HabitRPG Gems"
|
var description = !gift ? "HabitRPG Gems"
|
||||||
: gift.type=='gems' ? "HabitRPG Gems (Gift)"
|
: gift.type=='gems' ? "HabitRPG Gems (Gift)"
|
||||||
: gift.subscription.months + "mo. HabitRPG Subscription (Gift)";
|
: shared.content.subscriptionBlocks[gift.subscription.key].months + "mo. HabitRPG Subscription (Gift)";
|
||||||
var create_payment = {
|
var create_payment = {
|
||||||
"intent": "sale",
|
"intent": "sale",
|
||||||
"payer": {
|
"payer": {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ var paypal = require('paypal-rest-sdk');
|
||||||
var blocks = require('habitrpg-shared').content.subscriptionBlocks;
|
var blocks = require('habitrpg-shared').content.subscriptionBlocks;
|
||||||
var live = nconf.get('PAYPAL:mode')=='live';
|
var live = nconf.get('PAYPAL:mode')=='live';
|
||||||
|
|
||||||
var OP = 'get'; // list create update remove
|
var OP = 'create'; // list create update remove
|
||||||
|
|
||||||
paypal.configure({
|
paypal.configure({
|
||||||
'mode': nconf.get("PAYPAL:mode"), //sandbox or live
|
'mode': nconf.get("PAYPAL:mode"), //sandbox or live
|
||||||
|
|
@ -72,7 +72,7 @@ switch(OP) {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "create":
|
case "create":
|
||||||
paypal.billingPlan.create(blocks["12"].definition, function(err,plan){
|
paypal.billingPlan.create(blocks["google_6mo"].definition, function(err,plan){
|
||||||
if (err) return console.log(err);
|
if (err) return console.log(err);
|
||||||
if (plan.state == "ACTIVE")
|
if (plan.state == "ACTIVE")
|
||||||
return console.log({err:err, plan:plan});
|
return console.log({err:err, plan:plan});
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ var async = require('async');
|
||||||
var payments = require('./index');
|
var payments = require('./index');
|
||||||
var User = require('mongoose').model('User');
|
var User = require('mongoose').model('User');
|
||||||
var shared = require('habitrpg-shared');
|
var shared = require('habitrpg-shared');
|
||||||
|
var mongoose = require('mongoose');
|
||||||
|
var cc = require('coupon-code');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Setup Stripe response when posting payment
|
Setup Stripe response when posting payment
|
||||||
|
|
@ -17,16 +19,27 @@ exports.checkout = function(req, res, next) {
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
function(cb){
|
function(cb){
|
||||||
if (sub) {
|
if (sub) {
|
||||||
stripe.customers.create({
|
async.waterfall([
|
||||||
email: req.body.email,
|
function(cb2){
|
||||||
metadata: {uuid: user._id},
|
if (!sub.discount) return cb2(null, null);
|
||||||
card: token,
|
if (!req.query.coupon) return cb2('Please provide a coupon code for this plan.');
|
||||||
plan: sub.key
|
mongoose.model('Coupon').findOne({_id:cc.validate(req.query.coupon), event:sub.key}, cb2);
|
||||||
}, cb);
|
},
|
||||||
|
function(coupon, cb2){
|
||||||
|
if (sub.discount && !coupon) return cb2('Invalid coupon code.');
|
||||||
|
var customer = {
|
||||||
|
email: req.body.email,
|
||||||
|
metadata: {uuid: user._id},
|
||||||
|
card: token,
|
||||||
|
plan: sub.key
|
||||||
|
};
|
||||||
|
stripe.customers.create(customer, cb2);
|
||||||
|
}
|
||||||
|
], cb);
|
||||||
} else {
|
} else {
|
||||||
stripe.charges.create({
|
stripe.charges.create({
|
||||||
amount: !gift ? "500" //"500" = $5
|
amount: !gift ? "500" //"500" = $5
|
||||||
: gift.type=='subscription' ? ""+shared.content.subscriptionBlocks[gift.subscription.months].price*100
|
: gift.type=='subscription' ? ""+shared.content.subscriptionBlocks[gift.subscription.key].price*100
|
||||||
: ""+gift.gems.amount/4*100,
|
: ""+gift.gems.amount/4*100,
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
card: token
|
card: token
|
||||||
|
|
|
||||||
|
|
@ -257,37 +257,57 @@ api.update = function(req, res, next) {
|
||||||
};
|
};
|
||||||
|
|
||||||
api.cron = function(req, res, next) {
|
api.cron = function(req, res, next) {
|
||||||
var user = res.locals.user,
|
try{
|
||||||
progress = user.fns.cron(),
|
var user = res.locals.user,
|
||||||
ranCron = user.isModified(),
|
progress = user.fns.cron(),
|
||||||
quest = shared.content.quests[user.party.quest.key];
|
ranCron = user.isModified(),
|
||||||
|
quest = shared.content.quests[user.party.quest.key];
|
||||||
|
|
||||||
|
if (ranCron) res.locals.wasModified = true;
|
||||||
|
if (!ranCron) return next(null,user);
|
||||||
|
Group.tavernBoss(user,progress);
|
||||||
|
if (!quest) return user.save(next);
|
||||||
|
|
||||||
if (ranCron) res.locals.wasModified = true;
|
// FOR DEBUGGING, PLEASE IGNORE
|
||||||
if (!ranCron) return next(null,user);
|
var opStatus = null;
|
||||||
Group.tavernBoss(user,progress);
|
|
||||||
if (!quest) return user.save(next);
|
|
||||||
|
|
||||||
// If user is on a quest, roll for boss & player, or handle collections
|
// If user is on a quest, roll for boss & player, or handle collections
|
||||||
// FIXME this saves user, runs db updates, loads user. Is there a better way to handle this?
|
// FIXME this saves user, runs db updates, loads user. Is there a better way to handle this?
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
function(cb){
|
function(cb){
|
||||||
user.save(cb); // make sure to save the cron effects
|
opStatus = 'saveUser';
|
||||||
},
|
user.save(cb); // make sure to save the cron effects
|
||||||
function(saved, count, cb){
|
},
|
||||||
var type = quest.boss ? 'boss' : 'collect';
|
function(saved, count, cb){
|
||||||
Group[type+'Quest'](user,progress,cb);
|
opStatus = 'runQuest';
|
||||||
},
|
var type = quest.boss ? 'boss' : 'collect';
|
||||||
function(){
|
Group[type+'Quest'](user,progress,cb);
|
||||||
var cb = arguments[arguments.length-1];
|
},
|
||||||
// User has been updated in boss-grapple, reload
|
function(){
|
||||||
User.findById(user._id, cb);
|
var cb = arguments[arguments.length-1];
|
||||||
}
|
// User has been updated in boss-grapple, reload
|
||||||
], function(err, saved) {
|
User.findById(user._id, cb);
|
||||||
res.locals.user = saved;
|
}
|
||||||
next(err,saved);
|
], function(err, saved) {
|
||||||
user = progress = quest = null;
|
if(err) logging.loggly({
|
||||||
});
|
error: "Cron caught",
|
||||||
|
stack: (err.stack || err.message || err),
|
||||||
|
body: req.body, headers: req.header,
|
||||||
|
auth: (req.headers['x-api-user'] + ' | ' + req.headers['x-api-key']),
|
||||||
|
originalUrl: req.originalUrl,
|
||||||
|
opStatus: opStatus
|
||||||
|
});
|
||||||
|
res.locals.user = saved;
|
||||||
|
next(err,saved);
|
||||||
|
user = progress = quest = null;
|
||||||
|
});
|
||||||
|
}catch(e){
|
||||||
|
logging.loggly({
|
||||||
|
error: "Cron uncaught",
|
||||||
|
stack: e.stack || e
|
||||||
|
});
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,23 @@ var winston = require('winston');
|
||||||
require('winston-mail').Mail;
|
require('winston-mail').Mail;
|
||||||
require('winston-newrelic');
|
require('winston-newrelic');
|
||||||
|
|
||||||
var logger;
|
var logger, loggly;
|
||||||
|
|
||||||
|
if (nconf.get('LOGGLY:enabled')){
|
||||||
|
loggly = require('loggly').createClient({
|
||||||
|
token: nconf.get('LOGGLY:token'),
|
||||||
|
subdomain: nconf.get('LOGGLY:subdomain'),
|
||||||
|
auth: {
|
||||||
|
username: nconf.get('LOGGLY:username'),
|
||||||
|
password: nconf.get('LOGGLY:password')
|
||||||
|
},
|
||||||
|
//
|
||||||
|
// Optional: Tag to send with EVERY log message
|
||||||
|
//
|
||||||
|
tags: [('heroku-'+nconf.get('BASE_URL'))],
|
||||||
|
json: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (logger == null) {
|
if (logger == null) {
|
||||||
logger = new (winston.Logger)({});
|
logger = new (winston.Logger)({});
|
||||||
|
|
@ -49,3 +65,8 @@ module.exports.error = function(/* variable args */) {
|
||||||
if (logger)
|
if (logger)
|
||||||
logger.error.apply(logger, arguments);
|
logger.error.apply(logger, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports.loggly = function(/* variable args */){
|
||||||
|
if (loggly)
|
||||||
|
loggly.log.apply(loggly, arguments);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,13 @@ module.exports.errorHandler = function(err, req, res, next) {
|
||||||
"\n\nbody: " + JSON.stringify(req.body) +
|
"\n\nbody: " + JSON.stringify(req.body) +
|
||||||
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
|
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
|
||||||
logging.error(stack);
|
logging.error(stack);
|
||||||
|
logging.loggly({
|
||||||
|
error: "Uncaught error",
|
||||||
|
stack: (err.stack || err.message || err),
|
||||||
|
body: req.body, headers: req.header,
|
||||||
|
auth: (req.headers['x-api-user'] + ' | ' + req.headers['x-api-key']),
|
||||||
|
originalUrl: req.originalUrl
|
||||||
|
});
|
||||||
var message = err.message ? err.message : err;
|
var message = err.message ? err.message : err;
|
||||||
message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length);
|
message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length);
|
||||||
res.json(500,{err:message}); //res.end(err.message);
|
res.json(500,{err:message}); //res.end(err.message);
|
||||||
|
|
@ -173,12 +180,9 @@ module.exports.locals = function(req, res, next) {
|
||||||
language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
|
language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
|
||||||
|
|
||||||
var tavern = require('./models/group').tavern;
|
var tavern = require('./models/group').tavern;
|
||||||
res.locals.habitrpg = {
|
var envVars = _.pick(nconf.get(), 'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY'.split(' '));
|
||||||
NODE_ENV: nconf.get('NODE_ENV'),
|
res.locals.habitrpg = _.merge(envVars, {
|
||||||
BASE_URL: nconf.get('BASE_URL'),
|
|
||||||
GA_ID: nconf.get("GA_ID"),
|
|
||||||
IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
|
IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
|
||||||
STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'),
|
|
||||||
getManifestFiles: getManifestFiles,
|
getManifestFiles: getManifestFiles,
|
||||||
getBuildUrl: getBuildUrl,
|
getBuildUrl: getBuildUrl,
|
||||||
avalaibleLanguages: i18n.avalaibleLanguages,
|
avalaibleLanguages: i18n.avalaibleLanguages,
|
||||||
|
|
@ -193,11 +197,9 @@ module.exports.locals = function(req, res, next) {
|
||||||
siteVersion: siteVersion,
|
siteVersion: siteVersion,
|
||||||
Content: shared.content,
|
Content: shared.content,
|
||||||
mods: require('./models/user').mods,
|
mods: require('./models/user').mods,
|
||||||
FACEBOOK_KEY: nconf.get('FACEBOOK_KEY'),
|
|
||||||
|
|
||||||
tavern: tavern, // for world boss
|
tavern: tavern, // for world boss
|
||||||
worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {}
|
worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {}
|
||||||
};
|
});
|
||||||
|
|
||||||
// Put query-string party invitations into session to be handled later
|
// Put query-string party invitations into session to be handled later
|
||||||
try{
|
try{
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ var autoinc = require('mongoose-id-autoinc');
|
||||||
|
|
||||||
var CouponSchema = new mongoose.Schema({
|
var CouponSchema = new mongoose.Schema({
|
||||||
_id: {type: String, 'default': cc.generate},
|
_id: {type: String, 'default': cc.generate},
|
||||||
event: {type:String, enum:['wondercon']},
|
event: {type:String, enum:['wondercon','google_6mo']},
|
||||||
user: {type: 'String', ref: 'User'}
|
user: {type: 'String', ref: 'User'}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -18,38 +18,34 @@ CouponSchema.statics.generate = function(event, count, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
CouponSchema.statics.apply = function(user, code, next){
|
CouponSchema.statics.apply = function(user, code, next){
|
||||||
var _coupon,_user;
|
async.auto({
|
||||||
async.waterfall([
|
get_coupon: function (cb) {
|
||||||
function(cb) {
|
|
||||||
mongoose.model('Coupon').findById(cc.validate(code), cb);
|
mongoose.model('Coupon').findById(cc.validate(code), cb);
|
||||||
},
|
},
|
||||||
function(coupon, cb) {
|
apply_coupon: ['get_coupon', function (cb, results) {
|
||||||
_coupon = coupon;
|
if (!results.get_coupon) return cb("Invalid coupon code");
|
||||||
if (!coupon) return cb("Invalid coupon code");
|
if (results.get_coupon.user) return cb("Coupon already used");
|
||||||
if (coupon.user) return cb("Coupon already used");
|
switch (results.get_coupon.event) {
|
||||||
switch (coupon.event) {
|
|
||||||
case 'wondercon':
|
case 'wondercon':
|
||||||
user.items.gear.owned.eyewear_special_wondercon_red = true;
|
user.items.gear.owned.eyewear_special_wondercon_red = true;
|
||||||
user.items.gear.owned.eyewear_special_wondercon_black = true;
|
user.items.gear.owned.eyewear_special_wondercon_black = true;
|
||||||
user.items.gear.owned.back_special_wondercon_black = true;
|
user.items.gear.owned.back_special_wondercon_black = true;
|
||||||
user.items.gear.owned.back_special_wondercon_red = true;
|
user.items.gear.owned.back_special_wondercon_red = true;
|
||||||
user.items.gear.owned.body_special_wondercon_red = true;
|
user.items.gear.owned.body_special_wondercon_red = true;
|
||||||
user.items.gear.owned.body_special_wondercon_black = true;
|
user.items.gear.owned.body_special_wondercon_black = true;
|
||||||
user.items.gear.owned.body_special_wondercon_gold = true;
|
user.items.gear.owned.body_special_wondercon_gold = true;
|
||||||
user.extra = {signupEvent: 'wondercon'};
|
user.extra = {signupEvent: 'wondercon'};
|
||||||
user.save(cb);
|
user.save(cb);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
}],
|
||||||
function(user, count, cb){
|
expire_coupon: ['apply_coupon', function (cb, results) {
|
||||||
_user = user;
|
results.get_coupon.user = user._id;
|
||||||
_coupon.user = user._id;
|
results.get_coupon.save(cb);
|
||||||
_coupon.save(cb);
|
}]
|
||||||
}
|
}, function(err, results){
|
||||||
], function(err){
|
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
next(null,_user);
|
next(null,results.apply_coupon[0]);
|
||||||
_coupon = _user = null;
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,10 @@ var GroupSchema = new Schema({
|
||||||
# id: String
|
# id: String
|
||||||
# }]
|
# }]
|
||||||
*/
|
*/
|
||||||
|
leaderOnly: { // restrict group actions to leader (members can't do them)
|
||||||
|
challenges: {type:Boolean, 'default':false},
|
||||||
|
//invites: {type:Boolean, 'default':false}
|
||||||
|
},
|
||||||
memberCount: {type: Number, 'default': 0},
|
memberCount: {type: Number, 'default': 0},
|
||||||
challengeCount: {type: Number, 'default': 0},
|
challengeCount: {type: Number, 'default': 0},
|
||||||
balance: Number,
|
balance: Number,
|
||||||
|
|
@ -98,7 +101,9 @@ var chatDefaults = module.exports.chatDefaults = function(msg,user){
|
||||||
id: shared.uuid(),
|
id: shared.uuid(),
|
||||||
text: msg,
|
text: msg,
|
||||||
timestamp: +new Date,
|
timestamp: +new Date,
|
||||||
likes: {}
|
likes: {},
|
||||||
|
flags: {},
|
||||||
|
flagCount: 0
|
||||||
};
|
};
|
||||||
if (user) {
|
if (user) {
|
||||||
_.defaults(message, {
|
_.defaults(message, {
|
||||||
|
|
@ -316,6 +321,18 @@ GroupSchema.statics.bossQuest = function(user, progress, cb) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GroupSchema.methods.toJSON = function() {
|
||||||
|
var doc = this.toObject();
|
||||||
|
if(doc.chat){
|
||||||
|
doc.chat.forEach(function(msg){
|
||||||
|
msg.flags = {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports.schema = GroupSchema;
|
module.exports.schema = GroupSchema;
|
||||||
var Group = module.exports.model = mongoose.model("Group", GroupSchema);
|
var Group = module.exports.model = mongoose.model("Group", GroupSchema);
|
||||||
|
|
||||||
|
|
@ -330,4 +347,4 @@ Group.count({_id:'habitrpg'},function(err,ct){
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy:'public'
|
privacy:'public'
|
||||||
}).save();
|
}).save();
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,8 @@ var UserSchema = new Schema({
|
||||||
perfect: Number,
|
perfect: Number,
|
||||||
habitBirthday: Boolean,
|
habitBirthday: Boolean,
|
||||||
valentine: Number,
|
valentine: Number,
|
||||||
costumeContest: Boolean
|
costumeContest: Boolean,
|
||||||
|
nye: Number
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
blocked: Boolean,
|
blocked: Boolean,
|
||||||
|
|
@ -175,7 +176,9 @@ var UserSchema = new Schema({
|
||||||
snowball: {type: Number, 'default': 0},
|
snowball: {type: Number, 'default': 0},
|
||||||
spookDust: {type: Number, 'default': 0},
|
spookDust: {type: Number, 'default': 0},
|
||||||
valentine: Number,
|
valentine: Number,
|
||||||
valentineReceived: Array // array of strings, by sender name
|
valentineReceived: Array, // array of strings, by sender name
|
||||||
|
nye: Number,
|
||||||
|
nyeReceived: Array
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------- Animals -------------------
|
// -------------- Animals -------------------
|
||||||
|
|
@ -280,6 +283,7 @@ var UserSchema = new Schema({
|
||||||
automaticAllocation: Boolean,
|
automaticAllocation: Boolean,
|
||||||
allocationMode: {type:String, enum: ['flat','classbased','taskbased'], 'default': 'flat'},
|
allocationMode: {type:String, enum: ['flat','classbased','taskbased'], 'default': 'flat'},
|
||||||
costume: Boolean,
|
costume: Boolean,
|
||||||
|
dateFormat: {type: String, enum:['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'], 'default': 'MM/dd/yyyy'},
|
||||||
sleep: {type: Boolean, 'default': false},
|
sleep: {type: Boolean, 'default': false},
|
||||||
stickyHeader: {type: Boolean, 'default': true},
|
stickyHeader: {type: Boolean, 'default': true},
|
||||||
disableClasses: {type: Boolean, 'default': false},
|
disableClasses: {type: Boolean, 'default': false},
|
||||||
|
|
|
||||||
|
|
@ -577,11 +577,37 @@ module.exports = (swagger, v2) ->
|
||||||
middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup]
|
middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup]
|
||||||
action: groups.likeChatMessage
|
action: groups.likeChatMessage
|
||||||
|
|
||||||
|
"/groups/{gid}/chat/{mid}/flag":
|
||||||
|
spec:
|
||||||
|
method: 'POST'
|
||||||
|
description: "Flag a chat message"
|
||||||
|
parameters: [
|
||||||
|
path 'gid','Group id','string'
|
||||||
|
path 'mid','Message id','string'
|
||||||
|
]
|
||||||
|
middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup]
|
||||||
|
action: groups.flagChatMessage
|
||||||
|
|
||||||
|
"/groups/{gid}/chat/{mid}/clearflags":
|
||||||
|
spec:
|
||||||
|
method: 'POST'
|
||||||
|
description: "Clear flag count from message and unhide it"
|
||||||
|
parameters: [
|
||||||
|
path 'gid','Group id','string'
|
||||||
|
path 'mid','Message id','string'
|
||||||
|
]
|
||||||
|
middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup]
|
||||||
|
action: groups.clearFlagCount
|
||||||
|
|
||||||
# ---------------------------------
|
# ---------------------------------
|
||||||
# Members
|
# Members
|
||||||
# ---------------------------------
|
# ---------------------------------
|
||||||
"/members/{uuid}":
|
"/members/{uuid}:GET":
|
||||||
spec:{}
|
spec:
|
||||||
|
path: '/members/{uuid}'
|
||||||
|
description: "Get a member."
|
||||||
|
parameters: [path('uuid','Member ID','string')]
|
||||||
|
middleware: [auth.auth, i18n.getUserLanguage]
|
||||||
action: members.getMember
|
action: members.getMember
|
||||||
"/members/{uuid}/message":
|
"/members/{uuid}/message":
|
||||||
spec:
|
spec:
|
||||||
|
|
@ -589,7 +615,7 @@ module.exports = (swagger, v2) ->
|
||||||
description: 'Send a private message to a member'
|
description: 'Send a private message to a member'
|
||||||
parameters: [
|
parameters: [
|
||||||
path 'uuid', 'The UUID of the member to message', 'string'
|
path 'uuid', 'The UUID of the member to message', 'string'
|
||||||
body '', '{message: "The private message to send"}', 'object'
|
body '', '{"message": "The private message to send"}', 'object'
|
||||||
]
|
]
|
||||||
middleware: [auth.auth]
|
middleware: [auth.auth]
|
||||||
action: members.sendPrivateMessage
|
action: members.sendPrivateMessage
|
||||||
|
|
@ -608,7 +634,7 @@ module.exports = (swagger, v2) ->
|
||||||
description: 'Send a gift to a member'
|
description: 'Send a gift to a member'
|
||||||
parameters: [
|
parameters: [
|
||||||
path 'uuid', 'The UUID of the member', 'string'
|
path 'uuid', 'The UUID of the member', 'string'
|
||||||
body '', '{gems:{amount:Number, fromBalance:Boolean}, subscription:{months:Number}}', 'object'
|
body '', '{"type": "gems or subscription", "gems":{"amount":Number, "fromBalance":Boolean}, "subscription":{"months":Number}}', 'object'
|
||||||
]
|
]
|
||||||
middleware: [auth.auth]
|
middleware: [auth.auth]
|
||||||
action: members.sendGift
|
action: members.sendGift
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,9 @@ router.post("/stripe/subscribe/edit", auth.auth, i18n.getUserLanguage, payments.
|
||||||
//router.get("/stripe/subscribe", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead
|
//router.get("/stripe/subscribe", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead
|
||||||
router.get("/stripe/subscribe/cancel", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribeCancel);
|
router.get("/stripe/subscribe/cancel", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribeCancel);
|
||||||
|
|
||||||
|
router.post("/iap/android/verify", auth.authWithUrl, /*i18n.getUserLanguage, */payments.iapAndroidVerify);
|
||||||
|
router.post("/iap/ios/verify", /*auth.authWithUrl, i18n.getUserLanguage, */ payments.iapIosVerify);
|
||||||
|
|
||||||
|
router.get("/api/v2/coupons/valid-discount/:code", /*auth.authWithUrl, i18n.getUserLanguage, */ payments.validCoupon);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
@ -35,6 +35,8 @@ function getMailingInfo(user) {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.txnEmail = function(mailingInfo, emailType, variables){
|
module.exports.txnEmail = function(mailingInfo, emailType, variables){
|
||||||
|
var variables = [{name: 'BASE_URL', content: nconf.get('BASE_URL')}].concat(variables || []);
|
||||||
|
|
||||||
if (mailingInfo._id) mailingInfo = getMailingInfo(mailingInfo);
|
if (mailingInfo._id) mailingInfo = getMailingInfo(mailingInfo);
|
||||||
if (!mailingInfo.email) return;
|
if (!mailingInfo.email) return;
|
||||||
request({
|
request({
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ describe "API", ->
|
||||||
id: res.body.todos[0].id
|
id: res.body.todos[0].id
|
||||||
|
|
||||||
body:
|
body:
|
||||||
dateCompleted: moment().subtract("days", 4)
|
dateCompleted: moment().subtract(4, "days")
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
op: "updateTask"
|
op: "updateTask"
|
||||||
|
|
@ -149,7 +149,7 @@ describe "API", ->
|
||||||
id: res.body.todos[1].id
|
id: res.body.todos[1].id
|
||||||
|
|
||||||
body:
|
body:
|
||||||
dateCompleted: moment().subtract("days", 4)
|
dateCompleted: moment().subtract(4, "days")
|
||||||
}
|
}
|
||||||
]).end (res) ->
|
]).end (res) ->
|
||||||
expect(_.size(res.body.todos)).to.be 4
|
expect(_.size(res.body.todos)).to.be 4
|
||||||
|
|
@ -460,7 +460,7 @@ describe "API", ->
|
||||||
{
|
{
|
||||||
op: "update"
|
op: "update"
|
||||||
body:
|
body:
|
||||||
lastCron: moment().subtract("days", 1)
|
lastCron: moment().subtract(1, "days")
|
||||||
}
|
}
|
||||||
]).end (res) ->
|
]).end (res) ->
|
||||||
expect(res.body.party.quest.progress.up).to.be.above up
|
expect(res.body.party.quest.progress.up).to.be.above up
|
||||||
|
|
@ -532,7 +532,7 @@ describe "API", ->
|
||||||
{
|
{
|
||||||
op: "update"
|
op: "update"
|
||||||
body:
|
body:
|
||||||
lastCron: moment().subtract("days", 1)
|
lastCron: moment().subtract(1, "days")
|
||||||
}
|
}
|
||||||
]).end ->
|
]).end ->
|
||||||
cb()
|
cb()
|
||||||
|
|
@ -608,7 +608,7 @@ describe "API", ->
|
||||||
|
|
||||||
it "Handles unsubscription", (done) ->
|
it "Handles unsubscription", (done) ->
|
||||||
cron = ->
|
cron = ->
|
||||||
user.lastCron = moment().subtract("d", 1)
|
user.lastCron = moment().subtract(1, "d")
|
||||||
user.fns.cron()
|
user.fns.cron()
|
||||||
|
|
||||||
expect(user.purchased.plan.customerId).to.not.be.ok()
|
expect(user.purchased.plan.customerId).to.not.be.ok()
|
||||||
|
|
@ -624,7 +624,7 @@ describe "API", ->
|
||||||
cron()
|
cron()
|
||||||
expect(user.purchased.plan.customerId).to.be.ok()
|
expect(user.purchased.plan.customerId).to.be.ok()
|
||||||
expect(user.purchased.plan.dateTerminated).to.be.ok()
|
expect(user.purchased.plan.dateTerminated).to.be.ok()
|
||||||
user.purchased.plan.dateTerminated = moment().subtract("d", 2)
|
user.purchased.plan.dateTerminated = moment().subtract(2, "d")
|
||||||
cron()
|
cron()
|
||||||
expect(user.purchased.plan.customerId).to.not.be.ok()
|
expect(user.purchased.plan.customerId).to.not.be.ok()
|
||||||
payments.createSubscription user,
|
payments.createSubscription user,
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,12 @@ script(type='text/ng-template', id='partials/options.inventory.html')
|
||||||
li.equipment-tab(ng-class="{ active: $state.includes('options.inventory.equipment') }")
|
li.equipment-tab(ng-class="{ active: $state.includes('options.inventory.equipment') }")
|
||||||
a(ui-sref='options.inventory.equipment')
|
a(ui-sref='options.inventory.equipment')
|
||||||
=env.t('equipment')
|
=env.t('equipment')
|
||||||
li.equipment-tab(ng-class="{ active: $state.includes('options.inventory.timetravelers') }")
|
li(ng-class="{ active: $state.includes('options.inventory.timetravelers') }")
|
||||||
a(ui-sref='options.inventory.timetravelers')
|
a(ui-sref='options.inventory.timetravelers')
|
||||||
| Time Travelers
|
=env.t('timeTravelers')
|
||||||
|
li(ng-class="{ active: $state.includes('options.inventory.seasonalshop') }")
|
||||||
|
a(ui-sref='options.inventory.seasonalshop')
|
||||||
|
=env.t('seasonalShop')
|
||||||
|
|
||||||
.tab-content
|
.tab-content
|
||||||
.tab-pane.active
|
.tab-pane.active
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,42 @@ script(type='text/ng-template', id='partials/options.inventory.equipment.html')
|
||||||
div(ng-repeat='item in gear[klass]')
|
div(ng-repeat='item in gear[klass]')
|
||||||
button.customize-option(popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({params:{type:"costume", key:item.key}})', class='shop_{{::item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}')
|
button.customize-option(popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({params:{type:"costume", key:item.key}})', class='shop_{{::item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}')
|
||||||
|
|
||||||
|
script(type='text/ng-template', id='partials/options.inventory.seasonalshop.html')
|
||||||
|
.container-fluid
|
||||||
|
.stable.row
|
||||||
|
.col-md-2
|
||||||
|
.seasonalshop_winter2015
|
||||||
|
.col-md-10
|
||||||
|
.popover.static-popover.fade.right.in
|
||||||
|
.arrow
|
||||||
|
h3.popover-title!=env.t('seasonalShopTitle', {linkStart:"<a href='http://blog.habitrpg.com/who' target='_blank'>", linkEnd: "</a>"})
|
||||||
|
.popover-content
|
||||||
|
p!=env.t('seasonalShopText')
|
||||||
|
li.customize-menu.inventory-gear
|
||||||
|
menu.pets-menu(label='{{::label}}', ng-repeat='(set,label) in ::{candycane:env.t("candycaneSet"), ski:env.t("skiSet"), snowflake:env.t("snowflakeSet"), yeti:env.t("yetiSet")}')
|
||||||
|
div(ng-repeat='item in ::getSeasonalShopArray(set)' ng-class="{transparent: user.items.gear.owned[item.key]}")
|
||||||
|
button.customize-option(popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='purchase(item.type,item)', class='shop_{{::item.key}}')
|
||||||
|
.text-center
|
||||||
|
| {{((item.specialClass == "wizard") && (item.type == "weapon")) + 1}}
|
||||||
|
span.Pet_Currency_Gem1x.inline-gems
|
||||||
|
menu.pets-menu(label=env.t('quests'))
|
||||||
|
div(ng-repeat='quest in ::getSeasonalShopQuests()')
|
||||||
|
button.customize-option(popover="{{::quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : quest.notes() | htmlDecode}}", popover-title='{{::quest.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='buyQuest(quest.key)', class='inventory_quest_scroll', ng-class='::{locked: quest.previous && !user.achievements.quests[quest.previous]}')
|
||||||
|
p
|
||||||
|
| {{::quest.value}}
|
||||||
|
span.Pet_Currency_Gem1x.inline-gems
|
||||||
|
menu.pets-menu(label=env.t('seasonalItems'))
|
||||||
|
div
|
||||||
|
button.customize-option(popover='{{::Content.spells.special.snowball.notes()}}', popover-title='{{::Content.spells.special.snowball.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='purchase("special", Content.spells.special.snowball)', class='inventory_special_snowball')
|
||||||
|
p
|
||||||
|
| {{::Content.spells.special.snowball.value}}
|
||||||
|
span(class='shop_gold')
|
||||||
|
div
|
||||||
|
button.customize-option(popover='{{::Content.spells.special.nye.notes()}}', popover-title='{{::Content.spells.special.nye.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='castStart(Content.spells.special.nye)', class='inventory_special_nye')
|
||||||
|
p
|
||||||
|
| {{Content.spells.special.nye.value}}
|
||||||
|
span(class='shop_gold')
|
||||||
|
|
||||||
script(type='text/ng-template', id='partials/options.inventory.timetravelers.html')
|
script(type='text/ng-template', id='partials/options.inventory.timetravelers.html')
|
||||||
.container-fluid
|
.container-fluid
|
||||||
.stable.row(ng-if='user.purchased.plan.consecutive.trinkets <= 0')
|
.stable.row(ng-if='user.purchased.plan.consecutive.trinkets <= 0')
|
||||||
|
|
@ -83,7 +119,7 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
|
||||||
|
|
||||||
li.customize-menu
|
li.customize-menu
|
||||||
menu.pets-menu(label=env.t('food') + ' ({{foodCount}})')
|
menu.pets-menu(label=env.t('food') + ' ({{foodCount}})')
|
||||||
p(ng-show='foodCount < 1')=env.t('noFood')
|
p.muted(ng-show='foodCount < 1')=env.t('noFood')
|
||||||
div(ng-repeat='(food,points) in ownedItems(user.items.food)')
|
div(ng-repeat='(food,points) in ownedItems(user.items.food)')
|
||||||
button.customize-option(popover='{{::Content.food[food].notes()}}', popover-title='{{::Content.food[food].text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='chooseFood(food)', class='Pet_Food_{{::food}}')
|
button.customize-option(popover='{{::Content.food[food].notes()}}', popover-title='{{::Content.food[food].text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='chooseFood(food)', class='Pet_Food_{{::food}}')
|
||||||
.badge.badge-info.stack-count {{points}}
|
.badge.badge-info.stack-count {{points}}
|
||||||
|
|
@ -98,12 +134,10 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
|
||||||
+specialItem('snowball')
|
+specialItem('snowball')
|
||||||
+specialItem('spookDust')
|
+specialItem('spookDust')
|
||||||
|
|
||||||
//-menu.pets-menu(label=env.t('valentineCard'))
|
|
||||||
div(ng-if='user.items.special.valentineReceived[0]')
|
div(ng-if='user.items.special.valentineReceived[0]')
|
||||||
button.customize-option(popover="Valentine's Day Card from {{User.user.items.special.valentineReceived[0]}}", popover-trigger='mouseenter', popover-placement='right', ng-click='openModal("valentine")', class='inventory_special_valentine')
|
button.customize-option(popover="Valentine's Day Card from {{User.user.items.special.valentineReceived[0]}}", popover-trigger='mouseenter', popover-placement='right', ng-click='openModal("valentine")', class='inventory_special_valentine')
|
||||||
.badge.badge-info.stack-count {{user.items.special.valentineReceived.length}}
|
.badge.badge-info.stack-count {{user.items.special.valentineReceived.length}}
|
||||||
|
|
||||||
//-menu.pets-menu(label=env.t('subscriberItem'))
|
|
||||||
div(ng-if='user.purchased.plan.customerId || user.purchased.plan.mysteryItems.length')
|
div(ng-if='user.purchased.plan.customerId || user.purchased.plan.mysteryItems.length')
|
||||||
button.customize-option(popover=env.t('subscriberItemText'), popover-trigger='mouseenter', popover-placement='right', class='inventory_present', ng-click="user.ops.openMysteryItem({})")
|
button.customize-option(popover=env.t('subscriberItemText'), popover-trigger='mouseenter', popover-placement='right', class='inventory_present', ng-click="user.ops.openMysteryItem({})")
|
||||||
.badge.badge-info.stack-count {{user.purchased.plan.mysteryItems.length}}
|
.badge.badge-info.stack-count {{user.purchased.plan.mysteryItems.length}}
|
||||||
|
|
@ -112,6 +146,10 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
|
||||||
button.customize-option(popover=env.t('mysticHourglassPopover'), popover-trigger='mouseenter', popover-placement='right', class='inventory_special_trinket', ng-click="$state.go('options.inventory.timetravelers')")
|
button.customize-option(popover=env.t('mysticHourglassPopover'), popover-trigger='mouseenter', popover-placement='right', class='inventory_special_trinket', ng-click="$state.go('options.inventory.timetravelers')")
|
||||||
.badge.badge-info.stack-count {{user.purchased.plan.consecutive.trinkets}}
|
.badge.badge-info.stack-count {{user.purchased.plan.consecutive.trinkets}}
|
||||||
|
|
||||||
|
div(ng-if='user.items.special.nyeReceived[0]')
|
||||||
|
button.customize-option(popover="New Year's Card from {{User.user.items.special.nyeReceived[0]}}", popover-trigger='mouseenter', popover-placement='right', ng-click='openModal("nye")', class='inventory_special_nye')
|
||||||
|
.badge.badge-info.stack-count {{user.items.special.nyeReceived.length}}
|
||||||
|
|
||||||
.col-md-6
|
.col-md-6
|
||||||
h2=env.t('market')
|
h2=env.t('market')
|
||||||
.row-fluid
|
.row-fluid
|
||||||
|
|
@ -178,15 +216,6 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
|
||||||
p
|
p
|
||||||
| 4
|
| 4
|
||||||
span.Pet_Currency_Gem1x.inline-gems
|
span.Pet_Currency_Gem1x.inline-gems
|
||||||
mixin buySpecialSpell(k,gp)
|
|
||||||
div
|
|
||||||
button.customize-option(popover='{{::Content.spells.special.#{k}.notes()}}', popover-title='{{::Content.spells.special.#{k}.text()}}', popover-trigger='mouseenter', popover-placement='top', ng-click='purchase("special", Content.spells.special.#{k})', class='inventory_special_#{k}')
|
|
||||||
p
|
|
||||||
| {{::Content.spells.special.#{k}.value}}
|
|
||||||
span(class='#{gp ? "shop_gold" : "Pet_Currency_Gem1x inline-gems"}')
|
|
||||||
//-+buySpecialSpell('snowball',false)
|
|
||||||
//-+buySpecialSpell('spookDust',true)
|
|
||||||
|
|
||||||
div(ng-show='user.flags.rebirthEnabled')
|
div(ng-show='user.flags.rebirthEnabled')
|
||||||
button.customize-option(popover=env.t('rebirthPop'), popover-title=env.t('rebirthName'), popover-trigger='mouseenter', popover-placement='top', ng-click='openModal("rebirth")', class='rebirth_orb')
|
button.customize-option(popover=env.t('rebirthPop'), popover-title=env.t('rebirthName'), popover-trigger='mouseenter', popover-placement='top', ng-click='openModal("rebirth")', class='rebirth_orb')
|
||||||
p(ng-show='user.stats.lvl < 100')
|
p(ng-show='user.stats.lvl < 100')
|
||||||
|
|
@ -197,7 +226,7 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
|
||||||
p
|
p
|
||||||
| 4
|
| 4
|
||||||
span.Pet_Currency_Gem1x.inline-gems
|
span.Pet_Currency_Gem1x.inline-gems
|
||||||
div(ng-if='user.purchased.plan.customerId')
|
div(ng-if='user.purchased.plan.customerId', ng-class='::{transparent:(Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought) < 1}')
|
||||||
button.customize-option(popover=env.t('subGemPop'), popover-title=env.t('subGemName'), popover-trigger='mouseenter', popover-placement='top',ng-click='user.ops.purchase({params:{type:"gems",key:"gem"}})')
|
button.customize-option(popover=env.t('subGemPop'), popover-title=env.t('subGemName'), popover-trigger='mouseenter', popover-placement='top',ng-click='user.ops.purchase({params:{type:"gems",key:"gem"}})')
|
||||||
span.Pet_Currency_Gem.inline-gems
|
span.Pet_Currency_Gem.inline-gems
|
||||||
.badge.badge-success.stack-count {{Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought}}
|
.badge.badge-success.stack-count {{Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought}}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@ mixin customizeProfile(mobile)
|
||||||
if ~['limited','seasonal'].indexOf(status)
|
if ~['limited','seasonal'].indexOf(status)
|
||||||
.label.label-info.pull-right.hint(popover=limited, popover-title=env.t(status+'Edition'), popover-placement='right', popover-trigger='mouseenter')=env.t(status+'Edition')
|
.label.label-info.pull-right.hint(popover=limited, popover-title=env.t(status+'Edition'), popover-placement='right', popover-trigger='mouseenter')=env.t(status+'Edition')
|
||||||
menu(label=env.t(title))
|
menu(label=env.t(title))
|
||||||
+gemCost(2)
|
span(ng-hide='#{status=="disabled"} || #{showPath("user.purchased."+path, colors, "&&")}')
|
||||||
button.btn.btn-xs(ng-hide='#{status=="disabled"} || #{showPath("user.purchased."+path, colors, "||")}', ng-click='#{unlockPath(path, colors)}')!= env.t('unlockSet',{cost:5}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
+gemCost(2)
|
||||||
|
button.btn.btn-xs(ng-click='#{unlockPath(path, colors)}')!= env.t('unlockSet',{cost:5}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
||||||
each color in colors
|
each color in colors
|
||||||
button.customize-option(type='button', class='#{path=="skin" ? "skin_"+color : "customize-option hair hair_bangs_1_"+color}', ng-class='{locked: !user.purchased.#{path}["#{color}"]}', ng-if='#{status!="disabled"} || user.purchased.#{path}["#{color}"]', ng-click='unlock("#{path}.#{color}")')
|
button.customize-option(type='button', class='#{path=="skin" ? "skin_"+color : "customize-option hair hair_bangs_1_"+color}', ng-class='{locked: !user.purchased.#{path}["#{color}"]}', ng-if='#{status!="disabled"} || user.purchased.#{path}["#{color}"]', ng-click='unlock("#{path}.#{color}")')
|
||||||
|
|
||||||
|
|
@ -38,8 +39,9 @@ mixin customizeProfile(mobile)
|
||||||
|
|
||||||
menu(label=env.t('specialShirts'))
|
menu(label=env.t('specialShirts'))
|
||||||
- var specialShirts = ['convict', 'cross', 'fire', 'horizon', 'ocean', 'purple', 'rainbow', 'redblue', 'thunder', 'tropical', 'zombie']
|
- var specialShirts = ['convict', 'cross', 'fire', 'horizon', 'ocean', 'purple', 'rainbow', 'redblue', 'thunder', 'tropical', 'zombie']
|
||||||
+gemCost(2)
|
span(ng-hide='#{showPath("user.purchased.shirt", specialShirts, "&&")}')
|
||||||
button.btn.btn-xs(ng-hide='#{showPath("user.purchased.shirt", specialShirts, "&&")}', ng-click='#{unlockPath("shirt",specialShirts)}')!= env.t('unlockSet',{cost:5}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
+gemCost(2)
|
||||||
|
button.btn.btn-xs(ng-click='#{unlockPath("shirt",specialShirts)}')!= env.t('unlockSet',{cost:5}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
||||||
each shirt in specialShirts
|
each shirt in specialShirts
|
||||||
button.customize-option(type='button', class='{{user.preferences.size}}_shirt_'+shirt, ng-class='{locked: !user.purchased.shirt.'+shirt+'}', ng-click='unlock("shirt.'+shirt+'")')
|
button.customize-option(type='button', class='{{user.preferences.size}}_shirt_'+shirt, ng-class='{locked: !user.purchased.shirt.'+shirt+'}', ng-click='unlock("shirt.'+shirt+'")')
|
||||||
|
|
||||||
|
|
@ -61,6 +63,7 @@ mixin customizeProfile(mobile)
|
||||||
button(type='button', ng-if='user.purchased.hair.color.#{color}', class='customize-option hair hair_bangs_1_#{color}', ng-click='unlock("hair.color.#{color}")')
|
button(type='button', ng-if='user.purchased.hair.color.#{color}', class='customize-option hair hair_bangs_1_#{color}', ng-click='unlock("hair.color.#{color}")')
|
||||||
+buyPref('hair.color', ['rainbow','yellow','green','purple','blue','TRUred'], 'rainbowColors')
|
+buyPref('hair.color', ['rainbow','yellow','green','purple','blue','TRUred'], 'rainbowColors')
|
||||||
+buyPref('hair.color', ['candycorn','ghostwhite','halloween','midnight','pumpkin','zombie'], 'hauntedColors', 'disabled')
|
+buyPref('hair.color', ['candycorn','ghostwhite','halloween','midnight','pumpkin','zombie'], 'hauntedColors', 'disabled')
|
||||||
|
+buyPref('hair.color', ['aurora','festive','hollygreen','peppermint','snowy','winterstar'], 'winteryColors')
|
||||||
|
|
||||||
li.customize-menu
|
li.customize-menu
|
||||||
menu(label=env.t('bodyHair'))
|
menu(label=env.t('bodyHair'))
|
||||||
|
|
@ -80,8 +83,9 @@ mixin customizeProfile(mobile)
|
||||||
// Purchasable hairstyles
|
// Purchasable hairstyles
|
||||||
menu(label=env.t('hairSet1'))
|
menu(label=env.t('hairSet1'))
|
||||||
- var colors = [2,4,5,6,7,8]
|
- var colors = [2,4,5,6,7,8]
|
||||||
+gemCost(2)
|
span(ng-hide='#{showPath("user.purchased.hair.base", colors, "&&")}')
|
||||||
button.btn.btn-xs(ng-hide='#{showPath("user.purchased.hair", colors, "&&")}', ng-click='#{unlockPath("hair.base",colors)}')!= env.t('unlockSet',{cost:5}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
+gemCost(2)
|
||||||
|
button.btn.btn-xs(ng-click='#{unlockPath("hair.base",colors)}')!= env.t('unlockSet',{cost:5}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
||||||
each num in colors
|
each num in colors
|
||||||
button(class='hair_base_#{num}_{{user.preferences.hair.color}} customize-option', type='button', ng-class='{locked: !user.purchased.hair.base["#{num}"]}', ng-click='unlock("hair.base.#{num}")')
|
button(class='hair_base_#{num}_{{user.preferences.hair.color}} customize-option', type='button', ng-class='{locked: !user.purchased.hair.base["#{num}"]}', ng-click='unlock("hair.base.#{num}")')
|
||||||
|
|
||||||
|
|
@ -93,9 +97,10 @@ mixin customizeProfile(mobile)
|
||||||
button(class='hair_flower_#{num} customize-option', type='button', ng-click='set({"preferences.hair.flower":#{num}})')
|
button(class='hair_flower_#{num} customize-option', type='button', ng-click='set({"preferences.hair.flower":#{num}})')
|
||||||
|
|
||||||
li.customize-menu
|
li.customize-menu
|
||||||
h5=env.t('bodyFacialHair')
|
menu(label=env.t('bodyFacialHair'))
|
||||||
+gemCost(2)
|
span(ng-hide='user.purchased.hair.mustache["1"] && user.purchased.hair.mustache["2"] && user.purchased.hair.beard["1"] && user.purchased.hair.beard["2"] && user.purchased.hair.beard["3"]')
|
||||||
button.btn.btn-xs(ng-hide='user.purchased.hair.mustache["1"] && user.purchased.hair.mustache["2"] && user.purchased.hair.beard["1"] && user.purchased.hair.beard["2"] && user.purchased.hair.beard["3"]', ng-click='unlock("hair.mustache.1,hair.mustache.2,hair.beard.1,hair.beard.2,hair.beard.3")')!= env.t('unlockSet',{cost:5}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
+gemCost(2)
|
||||||
|
button.btn.btn-xs(ng-click='unlock("hair.mustache.1,hair.mustache.2,hair.beard.1,hair.beard.2,hair.beard.3")')!= env.t('unlockSet',{cost:5}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
||||||
|
|
||||||
// Beard
|
// Beard
|
||||||
menu(label=env.t('beard'))
|
menu(label=env.t('beard'))
|
||||||
|
|
@ -249,9 +254,9 @@ mixin backgrounds(mobile)
|
||||||
- var k = bgsKeys[i], bgs = env.Content.backgrounds[k];
|
- var k = bgsKeys[i], bgs = env.Content.backgrounds[k];
|
||||||
li.customize-menu
|
li.customize-menu
|
||||||
menu(label=env.t(k))
|
menu(label=env.t(k))
|
||||||
+gemCost(7)
|
span(ng-hide="ownsSet('background',#{JSON.stringify(bgs)})")
|
||||||
//-button.btn.btn-xs(ng-hide="ownsSet('background',Content.backgrounds['#{k}'])",ng-click="unlock(setKeys('background',Content.backgrounds['#{k}']))")!= env.t('unlockSet',{cost:15}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
+gemCost(7)
|
||||||
button.btn.btn-xs(ng-hide="ownsSet('background',#{JSON.stringify(bgs)})",ng-click="unlock(setKeys('background',#{JSON.stringify(bgs)}))")!= env.t('unlockSet',{cost:15}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
button.btn.btn-xs(ng-click="unlock(setKeys('background',#{JSON.stringify(bgs)}))")!= env.t('unlockSet',{cost:15}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
||||||
each bg,k in bgs
|
each bg,k in bgs
|
||||||
button.customize-option(type='button', class='background_#{k}', ng-class="user.purchased.background.#{k} ? 'background-unlocked' : 'background-locked'", ng-click='unlock("background.#{k}")', popover-title=bg.text(), popover=bg.notes(),popover-trigger='mouseenter')
|
button.customize-option(type='button', class='background_#{k}', ng-class="user.purchased.background.#{k} ? 'background-unlocked' : 'background-locked'", ng-click='unlock("background.#{k}")', popover-title=bg.text(), popover=bg.notes(),popover-trigger='mouseenter')
|
||||||
i.glyphicon.glyphicon-lock(ng-if="!user.purchased.background.#{k}")
|
i.glyphicon.glyphicon-lock(ng-if="!user.purchased.background.#{k}")
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,10 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
|
||||||
br
|
br
|
||||||
strong
|
strong
|
||||||
!=env.t('helpWithTranslation')
|
!=env.t('helpWithTranslation')
|
||||||
|
|
||||||
|
.form-horizontal
|
||||||
|
h5=env.t('dateFormat')
|
||||||
|
select.form-control(ng-model='user.preferences.dateFormat', ng-options='DF for DF in availableFormats', ng-change='set({"preferences.dateFormat": user.preferences.dateFormat})')
|
||||||
.checkbox
|
.checkbox
|
||||||
label
|
label
|
||||||
input(type='checkbox', ng-click='hideHeader() ', ng-checked='user.preferences.hideHeader!==true')
|
input(type='checkbox', ng-click='hideHeader() ', ng-checked='user.preferences.hideHeader!==true')
|
||||||
|
|
@ -200,7 +204,7 @@ mixin subPerks()
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
span.hint(popover=env.t('buyGemsGoldText', {gemCost: "{{Shared.planGemLimits.convRate}}", gemLimit: "{{Shared.planGemLimits.convCap}}"}),popover-trigger='mouseenter',popover-placement='right') #{env.t('buyGemsGold')}
|
span.hint(popover=env.t('buyGemsGoldText', {gemCost: "{{Shared.planGemLimits.convRate}}", gemLimit: "{{Shared.planGemLimits.convCap}}"}),popover-trigger='mouseenter',popover-placement='right') #{env.t('buyGemsGold')}
|
||||||
span.badge.badge-success(ng-show='_subscription.months>1') Cap raised to {{ [25 + user.purchased.plan.consecutive.gemCapExtra + Math.floor(_subscription.months/3*5), 50] | min }}
|
span.badge.badge-success(ng-show='_subscription.key!="basic_earned"') Cap raised to {{ [25 + user.purchased.plan.consecutive.gemCapExtra + Math.floor(Content.subscriptionBlocks[_subscription.key].months/3*5), 50] | min }}
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
span.hint(popover=env.t('retainHistoryText'),popover-trigger='mouseenter',popover-placement='right')=env.t('retainHistory')
|
span.hint(popover=env.t('retainHistoryText'),popover-trigger='mouseenter',popover-placement='right')=env.t('retainHistory')
|
||||||
|
|
@ -210,8 +214,8 @@ mixin subPerks()
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
span.hint(popover=env.t('mysteryItemText'),popover-trigger='mouseenter',popover-placement='right') #{env.t('mysteryItem')}
|
span.hint(popover=env.t('mysteryItemText'),popover-trigger='mouseenter',popover-placement='right') #{env.t('mysteryItem')}
|
||||||
div(ng-show='_subscription.months>1')
|
div(ng-show='_subscription.key!="basic_earned"')
|
||||||
.badge.badge-success +{{Math.floor(_subscription.months/3)}} Mystic Hourglass
|
.badge.badge-success +{{Math.floor(Content.subscriptionBlocks[_subscription.key].months/3)}} Mystic Hourglass
|
||||||
.small.muted Mystic Hourglasses allow purchasing a previous month's Mystery Item set.
|
.small.muted Mystic Hourglasses allow purchasing a previous month's Mystery Item set.
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
|
|
@ -224,7 +228,7 @@ script(id='partials/feature-matrix-check.html',type='text/ng-template')
|
||||||
|
|
||||||
script(id='partials/options.settings.subscription.html',type='text/ng-template')
|
script(id='partials/options.settings.subscription.html',type='text/ng-template')
|
||||||
//-h2=env.t('individualSub')
|
//-h2=env.t('individualSub')
|
||||||
.container-fluid(ng-init='_subscription={months:1}')
|
.container-fluid(ng-init='_subscription={key:"basic_earned"}')
|
||||||
.row
|
.row
|
||||||
.col-md-6
|
.col-md-6
|
||||||
h3 Benefits
|
h3 Benefits
|
||||||
|
|
@ -237,7 +241,7 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template')
|
||||||
| #{env.t('subCanceled')} <strong>{{moment(user.purchased.plan.dateTerminated).format('MM/DD/YYYY')}}</strong>
|
| #{env.t('subCanceled')} <strong>{{moment(user.purchased.plan.dateTerminated).format('MM/DD/YYYY')}}</strong>
|
||||||
tr(ng-if='!user.purchased.plan.dateTerminated'): td
|
tr(ng-if='!user.purchased.plan.dateTerminated'): td
|
||||||
h4=env.t('subscribed')
|
h4=env.t('subscribed')
|
||||||
p(ng-if='Payments.currentSub') Recurring ${{Payments.currentSub.price}} each {{Payments.currentSub.months}} Month(s) ({{user.purchased.plan.paymentMethod}})
|
p(ng-if='user.purchased.plan.planId') Recurring ${{Content.subscriptionBlocks[user.purchased.plan.planId].price}} each {{Content.subscriptionBlocks[user.purchased.plan.planId].months}} Month(s) ({{user.purchased.plan.paymentMethod}})
|
||||||
tr(ng-if='user.purchased.plan.extraMonths'): td
|
tr(ng-if='user.purchased.plan.extraMonths'): td
|
||||||
span.glyphicon.glyphicon-credit-card
|
span.glyphicon.glyphicon-credit-card
|
||||||
| You have {{user.purchased.plan.extraMonths | number:2}} months of subscription credit.
|
| You have {{user.purchased.plan.extraMonths | number:2}} months of subscription credit.
|
||||||
|
|
@ -250,16 +254,23 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template')
|
||||||
li Mystic Hourglasses: {{user.purchased.plan.consecutive.trinkets}}
|
li Mystic Hourglasses: {{user.purchased.plan.consecutive.trinkets}}
|
||||||
div(ng-if='!user.purchased.plan.customerId || (user.purchased.plan.customerId && user.purchased.plan.dateTerminated)')
|
div(ng-if='!user.purchased.plan.customerId || (user.purchased.plan.customerId && user.purchased.plan.dateTerminated)')
|
||||||
.form-group
|
.form-group
|
||||||
each block in env.Content.subscriptionBlocks
|
.radio(ng-repeat='block in Content.subscriptionBlocks | toArray | omit:"discount==true" | orderBy:"months"')
|
||||||
.radio
|
label
|
||||||
label
|
input(type="radio", name="subRadio", ng-value="block.key", ng-model='_subscription.key')
|
||||||
input(type="radio", name="subRadio", value="#{block.months}", ng-model='_subscription.months')
|
span(ng-show='block.original')
|
||||||
//-| #{block.months} Month(s) Recurring: $#{block.price} #{env.t('monthUSD')}
|
| Recurring <span class='line-through'>${{block.original}}</span> <span class='label label-success'>${{block.price}}</span> each {{block.months}} Month(s)
|
||||||
| Recurring $#{block.price} each #{block.months} Month(s)
|
span(ng-hide='block.original')
|
||||||
|
| Recurring ${{block.price}} each {{block.months}} Month(s)
|
||||||
|
|
||||||
|
.form-inline
|
||||||
|
.form-group
|
||||||
|
input.form-control(type='text', ng-model='_subscription.coupon', placeholder='Promo Code')
|
||||||
|
.form-group
|
||||||
|
button.btn.btn-small(type='button',ng-click='applyCoupon(_subscription.coupon)') Apply
|
||||||
|
|
||||||
h3(ng-if='(user.purchased.plan.customerId && user.purchased.plan.dateTerminated)') Resubscribe
|
h3(ng-if='(user.purchased.plan.customerId && user.purchased.plan.dateTerminated)') Resubscribe
|
||||||
a.btn.btn-primary(ng-click='Payments.showStripe({subscription:_subscription.months})', ng-disabled='!_subscription.months') Card
|
a.btn.btn-primary(ng-click='Payments.showStripe({subscription:_subscription.key, coupon:_subscription.coupon})', ng-disabled='!_subscription.key') Card
|
||||||
a.btn.btn-warning(href='/paypal/subscribe?_id={{user._id}}&apiToken={{user.apiToken}}&sub={{_subscription.months}}', ng-disabled='!_subscription.months') PayPal
|
a.btn.btn-warning(href='/paypal/subscribe?_id={{user._id}}&apiToken={{user.apiToken}}&sub={{_subscription.key}}{{_subscription.coupon ? "&coupon="+_subscription.coupon : ""}}', ng-disabled='!_subscription.key') PayPal
|
||||||
div(ng-if='user.purchased.plan.customerId')
|
div(ng-if='user.purchased.plan.customerId')
|
||||||
.btn.btn-primary(ng-if='!user.purchased.plan.dateTerminated && user.purchased.plan.paymentMethod=="Stripe"', ng-click='Payments.showStripeEdit()') Update Card
|
.btn.btn-primary(ng-if='!user.purchased.plan.dateTerminated && user.purchased.plan.paymentMethod=="Stripe"', ng-click='Payments.showStripeEdit()')=env.t('subUpdateCard')
|
||||||
.btn.btn-sm.btn-danger(ng-if='!user.purchased.plan.dateTerminated', ng-click='Payments.cancelSubscription()')=env.t('cancelSub')
|
.btn.btn-sm.btn-danger(ng-if='!user.purchased.plan.dateTerminated', ng-click='Payments.cancelSubscription()')=env.t('cancelSub')
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
mixin chatMessages(inbox)
|
mixin chatMessages(inbox)
|
||||||
ul.list-unstyled.tavern-chat
|
ul.list-unstyled.tavern-chat
|
||||||
- var ngRepeat = inbox ? 'message in user.inbox.messages | toArray:true | orderBy:"sort":true' : 'message in group.chat track by message.id'
|
- var ngRepeat = inbox ? 'message in user.inbox.messages | toArray:true | orderBy:"sort":true' : 'message in group.chat track by message.id'
|
||||||
li.chat-message(ng-repeat=ngRepeat, ng-class=':: {highlight: isUserMentioned(user,message) || message.uuid=="system", "own-message": user._id == message.uuid}', style='{{::message.sent ? "opacity:0.5" : ""}}')
|
li.chat-message(ng-repeat=ngRepeat, ng-class=':: {highlight: isUserMentioned(user,message) || message.uuid=="system", "own-message": user._id == message.uuid}', ng-if="!message.flagCount || message.flagCount < 2 || user.contributor.admin")
|
||||||
.scrollable-message
|
span.pull-right.text-danger(ng-if="user.contributor.admin && message.flagCount > 0")
|
||||||
|
| {{message.flagCount > 1 ? "Message Hidden" : "1 flag"}}
|
||||||
|
.scrollable-message(ng-class='{"transparent": message.sent || message.flags[user._id] || (user.contributor.admin && message.flagCount > 1)}')
|
||||||
span(ng-if='::message.user')
|
span(ng-if='::message.user')
|
||||||
a.label.label-default.chat-message.hidden-label
|
a.label.label-default.chat-message.hidden-label
|
||||||
span.glyphicon.glyphicon-arrow-right(ng-if='::message.sent')
|
span.glyphicon.glyphicon-arrow-right(ng-if='::message.sent')
|
||||||
|
|
|
||||||
span {{::message.user}}
|
span {{::message.user}}
|
||||||
span(ng-class='userAdminGlyphiconStyleFromLevel(message.contributor.level)')
|
span(ng-class='userAdminGlyphiconStyleFromLevel(message.contributor.level)')
|
||||||
// this invisible username label is here to push the message text far enough right that the visible label can be floated to this point without covering up any of the text
|
// this invisible username label is here to push the message text far enough right that the visible label can be floated to this point without covering up any of the text
|
||||||
markdown(ng-model='::message.text')
|
markdown(ng-model='::message.text')
|
||||||
| -
|
| -
|
||||||
span.muted.time(from-now='::message.timestamp' tooltip="{{::message.timestamp | date:'short'}}")
|
span.muted.time(from-now='::message.timestamp' tooltip="{{::message.timestamp | date:user.preferences.dateFormat.concat(' HH:mm:ss')}}")
|
||||||
unless inbox
|
unless inbox
|
||||||
span
|
span
|
||||||
a.label.label-default(ng-show='countExists(message.likes)', ng-class='{"label-success":message.likes[user._id]}', ng-click='likeChatMessage(group,message)') +{{countExists(message.likes)}}
|
a.label.label-default(ng-show='countExists(message.likes)', ng-class='{"label-success":message.likes[user._id]}', ng-click='likeChatMessage(group,message)') +{{countExists(message.likes)}}
|
||||||
|
|
@ -22,11 +25,15 @@ mixin chatMessages(inbox)
|
||||||
if inbox
|
if inbox
|
||||||
a(ng-click="quickReply(message.uuid)")
|
a(ng-click="quickReply(message.uuid)")
|
||||||
span.glyphicon.glyphicon-share-alt(tooltip=env.t('pm-reply'))
|
span.glyphicon.glyphicon-share-alt(tooltip=env.t('pm-reply'))
|
||||||
|
|
|
|
||||||
a(ng-click='#{inbox? "user.ops.deletePM({params:{id:message.$key}})" : "deleteChatMessage(group, message)"}', ng-if='#{inbox ? "true" : ":: user.contributor.admin || message.uuid == user.id"}')
|
a(ng-click='#{inbox? "user.ops.deletePM({params:{id:message.$key}})" : "deleteChatMessage(group, message)"}', ng-if='#{inbox ? "true" : ":: user.contributor.admin || message.uuid == user.id"}')
|
||||||
span.glyphicon.glyphicon-trash(tooltip=env.t('delete'))
|
span.glyphicon.glyphicon-trash(tooltip=env.t('delete'))
|
||||||
span.float-label
|
|
|
||||||
|
a(ng-click="flagChatMessage(group._id, message)", ng-if=':: user.contributor.admin || (!message.sent && user.flags.communityGuidelinesAccepted && message.uuid != user.id && message.uuid != "system")')
|
||||||
|
span.glyphicon.glyphicon-flag(tooltip="{{message.flags[user._id] ? env.t('abuseAlreadyReported') : env.t('abuseFlag')}}" ng-class='message.flags[user._id] ? "text-danger" : ""')
|
||||||
|
span.float-label(ng-class='::contribText(message.contributor, message.backer).length > 30 ? "long-title" : ""')
|
||||||
a.label.label-default.chat-message(ng-if=':: message.user', ng-class='::userLevelStyleFromLevel(message.contributor.level, message.backer.npc, style)', ng-click='clickMember(message.uuid, true)')
|
a.label.label-default.chat-message(ng-if=':: message.user', ng-class='::userLevelStyleFromLevel(message.contributor.level, message.backer.npc, style)', ng-click='clickMember(message.uuid, true)')
|
||||||
span.glyphicon.glyphicon-arrow-right(ng-if='::message.sent')
|
span.glyphicon.glyphicon-arrow-right(ng-if='::message.sent')
|
||||||
|
|
|
||||||
span(tooltip='{{::contribText(message.contributor, message.backer)}}') {{::message.user}}
|
span(tooltip='{{::contribText(message.contributor, message.backer)}}') {{::message.user}}
|
||||||
span(ng-class='::userAdminGlyphiconStyleFromLevel(message.contributor.level)')
|
span(ng-class='::userAdminGlyphiconStyleFromLevel(message.contributor.level)')
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,10 @@ form.col-md-12.form-horizontal(ng-submit='create(newGroup)')
|
||||||
span.gem-cost= '4 ' + env.t('gems')
|
span.gem-cost= '4 ' + env.t('gems')
|
||||||
p
|
p
|
||||||
small=env.t('gemCost')
|
small=env.t('gemCost')
|
||||||
|
.form-group
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
input(type='checkbox', ng-model='newGroup.leaderOnly.challenges')
|
||||||
|
| Only group leader can create challenges
|
||||||
.form-group(ng-show='type=="party"')
|
.form-group(ng-show='type=="party"')
|
||||||
input.btn.btn-default.form-control(type='submit', value=env.t('create'))
|
input.btn.btn-default.form-control(type='submit', value=env.t('create'))
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
|
||||||
.form-group
|
.form-group
|
||||||
label=env.t('logoUrl')
|
label=env.t('logoUrl')
|
||||||
input.form-control(type='url', placeholder=env.t('logoUrl'), ng-model='group.logo')
|
input.form-control(type='url', placeholder=env.t('logoUrl'), ng-model='group.logo')
|
||||||
|
.form-group
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
input(type='checkbox', ng-model='group.leaderOnly.challenges')
|
||||||
|
| Only group leader can create challenges
|
||||||
|
|
||||||
h4=env.t('assignLeader')
|
h4=env.t('assignLeader')
|
||||||
select#group-leader-selection(ng-model='group._newLeader', ng-options='member.profile.name for member in group.members')
|
select#group-leader-selection(ng-model='group._newLeader', ng-options='member.profile.name for member in group.members')
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ script(type='text/ng-template', id='partials/options.social.inbox.html')
|
||||||
.checkbox
|
.checkbox
|
||||||
label
|
label
|
||||||
input(type='checkbox', ng-model='user.inbox.optOut', ng-change='set({"inbox.optOut": user.inbox.optOut?true: false})')
|
input(type='checkbox', ng-model='user.inbox.optOut', ng-change='set({"inbox.optOut": user.inbox.optOut?true: false})')
|
||||||
|
|
|
||||||
span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('optOutPopover'))=env.t('optOut')
|
span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('optOutPopover'))=env.t('optOut')
|
||||||
|
|
||||||
script(type='text/ng-template', id='partials/options.social.tavern.html')
|
script(type='text/ng-template', id='partials/options.social.tavern.html')
|
||||||
|
|
@ -104,13 +105,13 @@ script(type='text/ng-template', id='partials/options.social.html')
|
||||||
a(ui-sref='options.social.party')
|
a(ui-sref='options.social.party')
|
||||||
=env.t('party')
|
=env.t('party')
|
||||||
li(ng-class="{ active: $state.includes('options.social.guilds') }")
|
li(ng-class="{ active: $state.includes('options.social.guilds') }")
|
||||||
a(ui-sref='options.social.guilds')
|
a(ui-sref='options.social.guilds.public')
|
||||||
=env.t('guilds')
|
=env.t('guilds')
|
||||||
li(ng-class="{ active: $state.includes('options.social.challenges') }")
|
li(ng-class="{ active: $state.includes('options.social.challenges') }")
|
||||||
a(ui-sref='options.social.challenges')
|
a(ui-sref='options.social.challenges')
|
||||||
=env.t('challenges')
|
=env.t('challenges')
|
||||||
li(ng-class="{ active: $state.includes('options.social.hall') }")
|
li(ng-class="{ active: $state.includes('options.social.hall') }")
|
||||||
a(ui-sref='options.social.hall')
|
a(ui-sref='options.social.hall.heroes')
|
||||||
=env.t('hall')
|
=env.t('hall')
|
||||||
|
|
||||||
.tab-content
|
.tab-content
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
span(ng-hide='user.preferences.sleep')=env.t('innCheckIn')
|
span(ng-hide='user.preferences.sleep')=env.t('innCheckIn')
|
||||||
=env.t('danielText2')
|
=env.t('danielText2')
|
||||||
.alert.alert-info(ng-show='user.preferences.sleep')
|
.alert.alert-info(ng-show='user.preferences.sleep')
|
||||||
=env.t('innText')
|
=env.t('innText',{name:"{{user.profile.name}}"})
|
||||||
|
|
||||||
// Resources
|
// Resources
|
||||||
.panel.panel-default
|
.panel.panel-default
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,11 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}')
|
||||||
li
|
li
|
||||||
a(ui-sref='options.social.party')=env.t('party')
|
a(ui-sref='options.social.party')=env.t('party')
|
||||||
li
|
li
|
||||||
a(ui-sref='options.social.guilds')=env.t('guilds')
|
a(ui-sref='options.social.guilds.public')=env.t('guilds')
|
||||||
li
|
li
|
||||||
a(ui-sref='options.social.challenges')=env.t('challenges')
|
a(ui-sref='options.social.challenges')=env.t('challenges')
|
||||||
li
|
li
|
||||||
a(ui-sref='options.social.hall')=env.t('hall')
|
a(ui-sref='options.social.hall.heroes')=env.t('hall')
|
||||||
ul.toolbar-submenu
|
ul.toolbar-submenu
|
||||||
li
|
li
|
||||||
a(ui-sref='options.inventory.drops')=env.t('market')
|
a(ui-sref='options.inventory.drops')=env.t('market')
|
||||||
|
|
@ -49,7 +49,9 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}')
|
||||||
li
|
li
|
||||||
a(ui-sref='options.inventory.equipment')=env.t('equipment')
|
a(ui-sref='options.inventory.equipment')=env.t('equipment')
|
||||||
li
|
li
|
||||||
a(ui-sref='options.inventory.timetravelers') Time Travelers
|
a(ui-sref='options.inventory.timetravelers')=env.t('timeTravelers')
|
||||||
|
li
|
||||||
|
a(ui-sref='options.inventory.seasonalshop')=env.t('seasonalShop')
|
||||||
ul.toolbar-submenu
|
ul.toolbar-submenu
|
||||||
li
|
li
|
||||||
a(target="_blank" ng-href='http://data.habitrpg.com?uuid={{user._id}}')=env.t('dataTool')
|
a(target="_blank" ng-href='http://data.habitrpg.com?uuid={{user._id}}')=env.t('dataTool')
|
||||||
|
|
@ -110,11 +112,11 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}')
|
||||||
li
|
li
|
||||||
a(ui-sref='options.social.party')=env.t('party')
|
a(ui-sref='options.social.party')=env.t('party')
|
||||||
li
|
li
|
||||||
a(ui-sref='options.social.guilds')=env.t('guilds')
|
a(ui-sref='options.social.guilds.public')=env.t('guilds')
|
||||||
li
|
li
|
||||||
a(ui-sref='options.social.challenges')=env.t('challenges')
|
a(ui-sref='options.social.challenges')=env.t('challenges')
|
||||||
li
|
li
|
||||||
a(ui-sref='options.social.hall')=env.t('hall')
|
a(ui-sref='options.social.hall.heroes')=env.t('hall')
|
||||||
li.toolbar-button-dropdown
|
li.toolbar-button-dropdown
|
||||||
a(ui-sref='options.inventory.drops')
|
a(ui-sref='options.inventory.drops')
|
||||||
span=env.t('inventory')
|
span=env.t('inventory')
|
||||||
|
|
@ -131,7 +133,9 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}')
|
||||||
li
|
li
|
||||||
a(ui-sref='options.inventory.equipment')=env.t('equipment')
|
a(ui-sref='options.inventory.equipment')=env.t('equipment')
|
||||||
li
|
li
|
||||||
a(ui-sref='options.inventory.timetravelers') Time Travelers
|
a(ui-sref='options.inventory.timetravelers')=env.t('timeTravelers')
|
||||||
|
li
|
||||||
|
a(ui-sref='options.inventory.seasonalshop')=env.t('seasonalShop')
|
||||||
li.toolbar-button-dropdown
|
li.toolbar-button-dropdown
|
||||||
a(target="_blank" ng-href='http://data.habitrpg.com?uuid={{user._id}}')
|
a(target="_blank" ng-href='http://data.habitrpg.com?uuid={{user._id}}')
|
||||||
span=env.t('data')
|
span=env.t('data')
|
||||||
|
|
|
||||||
|
|
@ -27,4 +27,30 @@ script(id='modals/valentine.html', type='text/ng-template')
|
||||||
p
|
p
|
||||||
small For enduring such a saccharine poem, you both receive the "Adoring Friends" badge!
|
small For enduring such a saccharine poem, you both receive the "Adoring Friends" badge!
|
||||||
.modal-footer
|
.modal-footer
|
||||||
button.btn.btn-default(ng-click='user.ops.readValentine({});$close()')=env.t('ok')
|
button.btn.btn-default(ng-click='user.ops.readValentine({});$close()')=env.t('ok')
|
||||||
|
|
||||||
|
// New Year's
|
||||||
|
script(id='modals/nye.html', type='text/ng-template')
|
||||||
|
.modal-header
|
||||||
|
h4
|
||||||
|
.inventory_special_nye.pull-right
|
||||||
|
=env.t('nyeCard')
|
||||||
|
.modal-body
|
||||||
|
.bg-info(style='padding:10px')
|
||||||
|
p To: {{user.profile.name}}, From: {{user.items.special.nyeReceived[0]}}
|
||||||
|
hr
|
||||||
|
ul.list-unstyled(ng-switch='::Math.floor(Math.random()*5)')
|
||||||
|
li(ng-switch-when='0')
|
||||||
|
=env.t('newYear0')
|
||||||
|
li(ng-switch-when='1')
|
||||||
|
=env.t('newYear1')
|
||||||
|
li(ng-switch-when='2')
|
||||||
|
=env.t('newYear2')
|
||||||
|
li(ng-switch-when='3')
|
||||||
|
=env.t('newYear3')
|
||||||
|
li(ng-switch-default)
|
||||||
|
=env.t('newYear4')
|
||||||
|
p
|
||||||
|
small For celebrating the new year together, you both receive the "Auld Acquaintance" badge!
|
||||||
|
.modal-footer
|
||||||
|
button.btn.btn-default(ng-click='user.ops.readNYE({});$close()')=env.t('ok')
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ script(type='text/ng-template', id='modals/member.html')
|
||||||
|
|
|
|
||||||
=env.t('memberSince')
|
=env.t('memberSince')
|
||||||
|
|
|
|
||||||
| {{::timestamp(profile.auth.timestamps.created)}} -
|
| {{::profile.auth.timestamps.created | date:user.preferences.dateFormat}} -
|
||||||
li(ng-show='::profile.auth.timestamps.loggedin')
|
li(ng-show='::profile.auth.timestamps.loggedin')
|
||||||
|
|
|
|
||||||
=env.t('lastLoggedIn')
|
=env.t('lastLoggedIn')
|
||||||
|
|
|
|
||||||
| {{::timestamp(profile.auth.timestamps.loggedin)}} -
|
| {{::profile.auth.timestamps.loggedin | date:user.preferences.dateFormat}} -
|
||||||
h3=env.t('stats')
|
h3=env.t('stats')
|
||||||
.label.label-info {{:: {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer")}[profile.stats.class] }}
|
.label.label-info {{:: {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer")}[profile.stats.class] }}
|
||||||
include ../profiles/stats
|
include ../profiles/stats
|
||||||
|
|
@ -46,7 +46,7 @@ script(type='text/ng-template', id='modals/private-message.html')
|
||||||
.modal-header
|
.modal-header
|
||||||
h4=env.t('pmHeading', {name: "{{profile.profile.name}}"})
|
h4=env.t('pmHeading', {name: "{{profile.profile.name}}"})
|
||||||
.modal-body
|
.modal-body
|
||||||
textarea.form-control(type='text',ng-model='_message')
|
textarea.form-control(type='text',rows='5',ng-model='_message')
|
||||||
.modal-footer
|
.modal-footer
|
||||||
button.btn.btn-primary(ng-click='sendPrivateMessage(profile._id, _message)')=env.t("send")
|
button.btn.btn-primary(ng-click='sendPrivateMessage(profile._id, _message)')=env.t("send")
|
||||||
button.btn.btn-default(ng-click='$close()')=env.t('cancel')
|
button.btn.btn-default(ng-click='$close()')=env.t('cancel')
|
||||||
|
|
@ -75,11 +75,10 @@ script(type='text/ng-template', id='modals/send-gift.html')
|
||||||
.panel-heading Subscription
|
.panel-heading Subscription
|
||||||
.panel-body
|
.panel-body
|
||||||
.form-group
|
.form-group
|
||||||
each block in env.Content.subscriptionBlocks
|
.radio(ng-repeat='block in Content.subscriptionBlocks | toArray | omit:"discount==true" | orderBy:"months"')
|
||||||
.radio
|
label
|
||||||
label
|
input(type="radio", name="subRadio", ng-value="block.key", ng-model='gift.subscription.key')
|
||||||
input(type="radio", name="subRadio", value="#{block.months}", ng-model='gift.subscription.months')
|
| {{block.months}} Month(s): ${{block.price}}
|
||||||
| #{block.months} Month(s): $#{block.price}
|
|
||||||
|
|
||||||
textarea.form-control(rows='3', ng-model='gift.message', placeholder='Personal message (optional)')
|
textarea.form-control(rows='3', ng-model='gift.message', placeholder='Personal message (optional)')
|
||||||
|
|
||||||
|
|
@ -88,4 +87,17 @@ script(type='text/ng-template', id='modals/send-gift.html')
|
||||||
button.btn.btn-primary(ng-show=fromBal, ng-click='sendGift(profile._id, gift)')=env.t("send")
|
button.btn.btn-primary(ng-show=fromBal, ng-click='sendGift(profile._id, gift)')=env.t("send")
|
||||||
a.btn.btn-primary(ng-hide=fromBal, ng-click='Payments.showStripe({gift:gift, uuid:profile._id})')=env.t('card')
|
a.btn.btn-primary(ng-hide=fromBal, ng-click='Payments.showStripe({gift:gift, uuid:profile._id})')=env.t('card')
|
||||||
a.btn.btn-warning(ng-hide=fromBal, href='/paypal/checkout?_id={{::user._id}}&apiToken={{::user.apiToken}}&gift={{Payments.encodeGift(profile._id, gift)}}') PayPal
|
a.btn.btn-warning(ng-hide=fromBal, href='/paypal/checkout?_id={{::user._id}}&apiToken={{::user.apiToken}}&gift={{Payments.encodeGift(profile._id, gift)}}') PayPal
|
||||||
button.btn.btn-default(ng-click='$close()')=env.t('cancel')
|
button.btn.btn-default(ng-click='$close()')=env.t('cancel')
|
||||||
|
|
||||||
|
script(type='text/ng-template', id='modals/abuse-flag.html')
|
||||||
|
.modal-header
|
||||||
|
h4!=env.t('abuseFlagModalHeading', {name: "<span class='text-danger'>{{profile.profile.name}}</span>"})
|
||||||
|
.modal-body
|
||||||
|
blockquote
|
||||||
|
markdown(ng-model="abuseObject.text")
|
||||||
|
p!=env.t('abuseFlagModalBody', {firstLinkStart: "<a href='/static/community-guidelines' target='_blank'>", secondLinkStart: "<a href='/static/terms' target='_blank'>", linkEnd: "</a>"})
|
||||||
|
.modal-footer
|
||||||
|
button.pull-left.btn.btn-danger(ng-click='clearFlagCount(abuseObject, groupId)', ng-if='user.contributor.admin && abuseObject.flagCount >= 2')
|
||||||
|
| Reset Flag Count
|
||||||
|
button.btn.btn-primary(ng-click='$close()')=env.t('cancel')
|
||||||
|
button.btn.btn-default(ng-click='reportAbuse(user, abuseObject, groupId)')=env.t("abuseFlagModalButton")
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ script(type='text/ng-template', id='modals/questCompleted.html')
|
||||||
h4 "{{::Content.quests[user.party.quest.completed].text()}}"
|
h4 "{{::Content.quests[user.party.quest.completed].text()}}"
|
||||||
=env.t('completed')
|
=env.t('completed')
|
||||||
.modal-body
|
.modal-body
|
||||||
|
div(ng-class='::Content.quests[user.party.quest.completed].completion() ? "pull-right-sm" : ""', class='col-centered quest_{{user.party.quest.completed}}')
|
||||||
p(ng-bind-html='::Content.quests[user.party.quest.completed].completion()')
|
p(ng-bind-html='::Content.quests[user.party.quest.completed].completion()')
|
||||||
quest-rewards(key='{{user.party.quest.completed}}', header=env.t('youReceived'))
|
quest-rewards(key='{{user.party.quest.completed}}', header=env.t('youReceived'))
|
||||||
.modal-footer
|
.modal-footer
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,114 @@
|
||||||
h5 PENGUIN PET QUEST AND WINTER PLOT-LINE
|
h5 NEW YEAR'S EVE CELEBRATION: Party Hats, New Year's Cards, Snowballs, and Last Chance for Penguin Item Set
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
h5 Penguin Pet Quest
|
h5 Party Hats
|
||||||
.quest_penguin.pull-right
|
.promo_partyhats.pull-right
|
||||||
p Habiticans wanted to go ice-skating, but instead, a giant penguin is freezing everything in sight! All we wanted was to go ice-skating... Can you get this penguin to chill out? If so, you'll be rewarded with some penguins of your own!
|
p In honor of the new year, some free Party Hats are available in the Rewards store! New users get the ever-handsome Absurd Party Hat, and users who already received one last year get the Silly Party Hat. These hats will be available to purchase until January 31st, but once you've bought them, you'll have them forever. Enjoy!
|
||||||
p.small.muted by Melynnrose, Breadstrings, Rattify, Painter de Cluster, Daniel the Bard, and Leephon
|
p.small.muted by Lemoness and SabreCat
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
h5 Winter Plot-Line
|
h5 New Year's Cards (Until Jan 1st Only!)
|
||||||
p Lemoness enters the Tavern with worrying news from the far north of Habitica. "Nobody's heard from the Stoïkalm Steppes for over a week," she says. "It's hard to imagine anything troubling the citizens there, since it's such a placid part of the continent... But just in case, maybe I should pay a visit." Sounds like a good plan to us!
|
.inventory_special_nye.pull-right
|
||||||
|
p Until January 1st only, the <a href='https://habitrpg.com/#/options/inventory/seasonalshop' target='_blank'>Seasonal Shop</a> is stocking New Year's Cards! Now you can send cards to your friends (and yourself) to wish them a Happy Habit New Year. All senders and recipients will receive the Auld Acquaintance badge! When you receive a card, it will appear in your <a href='https://habitrpg.com/#/options/inventory/drops' target='_blank'>Inventory</a>. Click it to receive a seasonal message!
|
||||||
|
p.small.muted by Lemoness and SabreCat
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Snowballs
|
||||||
|
.inventory_special_snowball.pull-right
|
||||||
|
p The <a href='https://habitrpg.com/#/options/inventory/seasonalshop' target='_blank'>Seasonal Shop</a> is also stocking Snowballs for gold! Throw them at your friends to have an exciting effect. Anyone hit with a snowball earns the Annoying Friends badge. The results of being hit with a Snowball will last until the end of your day, but you can also reverse them early by buying Salt from the Rewards column. Snowballs are available until January 31st.
|
||||||
|
p.small.muted by Shaner, Lemoness, and SabreCat
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Last Chance for Penguin Item Set
|
||||||
|
p Reminder: this is the final day to <a href='https://habitrpg.com/#/options/settings/subscription' target='_blank'>subscribe</a> and receive the Penguin Item Set! If you want the Penguin Hat or the Penguin Suit, now's the time! Thanks so much for your support <3
|
||||||
|
|
||||||
hr
|
hr
|
||||||
a(href='/static/old-news', target='_blank') Read older news
|
a(href='/static/old-news', target='_blank') Read older news
|
||||||
|
|
||||||
mixin oldNews
|
mixin oldNews
|
||||||
|
h5 12/25/2014 - December Subscriber Item Set and Seasonal Shop! Plus, Flagging Posts and Winter Plot-Line Continues
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 December Subscriber Item Set
|
||||||
|
.promo_mystery_201412.pull-right
|
||||||
|
p The December Subscriber Item has been revealed: the Penguin Item Set! All December subscribers will receive the Penguin Hat and the Penguin Suit. You still have six days to <a href='https://habitrpg.com/#/options/settings/subscription' target='_blank'>subscribe</a>) and receive the item set! Thank you so much for your support - we really do rely on you to keep HabitRPG free to use and running smoothly.
|
||||||
|
p.small.muted by Lemoness
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Seasonal Shop: Seasonal Outfits and Quests
|
||||||
|
.seasonalshop_winter2015.pull-right
|
||||||
|
p The <a href='https://habitrpg.com/#/options/inventory/seasonalshop'>Seasonal Shop</a> has opened! The Seasonal Sorceress is stocking the seasonal edition versions of last year's winter outfits, now available for Gems instead of Gold, and the two winter quests, Trapper Santa and Find the Cub. The Seasonal Shop will only be open until January 31st, so don't wait!
|
||||||
|
p.small.muted by SabreCat and Lemoness
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Flagging Posts
|
||||||
|
p You can now report inappropriate posts to moderators simply by clicking the new flag button next to the post. You should only report posts that violate the Community Guidelines and/or Terms of Service. Thanks for helping us to keep Habitica safe and pleasant for everybody!
|
||||||
|
p.small.muted by Alys, Blade, and Matteo
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Winter Plot-Line Continues
|
||||||
|
p SabreCat's news is dire. "Most of my sabertooth friends have been impossible to reach, but one thing is clear: the prides have been disappearing from the Steppes. There are also reports that something drove the mammoths to early migration and disturbed the hibernation of the terrible lizards."
|
||||||
|
p He wraps his cloak around himself as another blast of frigid wind roars through the streets. An icy winter gale has been blowing from the north, rattling the window panes and setting the pets and mounts to trembling and howling.
|
||||||
|
p "I've never seen anything like it!" says Matt the Beast Master. "Something is terrifying all my animals - even the cacti, who are normally so mighty and brave! For something to frighten a cactus..." He shakes his head.
|
||||||
|
p The stress level in Habitica is mounting.
|
||||||
|
p.small.muted Missed the previous Winter Plot-line? Catch up on the story <a href='http://habitrpg.wikia.com/wiki/Winter_Wonderland#Winter_Mystery_Plot' target='_blank'>here</a>!
|
||||||
|
p.small.muted by Lemoness
|
||||||
|
h5 12/21/2014 - Winter Wonderland Begins, Winter Class Outfits, Wintery Hair Colors, and NPC Decorations!
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Winter Wonderland Begins!
|
||||||
|
p Winter has arrived, and the snow is gently drifting down over Habit City. Come celebrate with us!
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Winter Class Outfits
|
||||||
|
.promo_winterclasses2015.pull-right
|
||||||
|
p From now until January 31st, limited edition outfits are available in the Rewards column. Depending on your class, you can be a Soothing Skater, Mage of the North, Gingerbread Warrior, or Icicle Drake! You'd better get productive to earn enough gold before they disappear. Good luck!
|
||||||
|
p.small.muted by Lemoness
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Wintery Hair Colors
|
||||||
|
.promo_winteryhair.pull-right
|
||||||
|
p The Seasonal Edition Wintery Hair Colors are now available for purchase in the avatar customizations page! Now you can dye your avatar's hair Holly Green, Winter Star, Snowy, Peppermint, Aurora, or Festive.
|
||||||
|
p Seasonal Edition items recur unchanged every year, but they are only available to purchase during a short period of time. This is different from Limited Edition Items, which only recur if something is changed, such as the art or the price. These hair colors may remind some of you of the Holiday Hair Colors that were available last winter. The Holiday Hair Colors have been Retired in favor of the similar Seasonal Edition Wintery Hair Colors. Read more about the difference between Seasonal and Limited Edition items <a href='http://habitrpg.wikia.com/wiki/Item_Availability'>here</a>!
|
||||||
|
p.small.muted by Lemoness, crystalphoenix, and mariahm
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 NPC Decorations
|
||||||
|
.npc_alex.pull-right
|
||||||
|
p Looks like the NPCs are really getting in to the cheery winter mood around the site. Who wouldn't? After all, there's plenty more to come!
|
||||||
|
p.small.muted by Lemoness
|
||||||
|
h5 12/17/2014 - Android App Update, Seasonal Shop, and Winter Plot-Line Continues
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Android App Update: December Art and Buying Gems!
|
||||||
|
p The December backgrounds and penguin pet quest are now visible in the Android mobile app! Also, we’ve made it possible to buy gems directly from the app. Now you don’t have to switch to the website to stock up!
|
||||||
|
p You can get the Android app <a href='https://play.google.com/store/apps/details?id=com.ocdevel.habitrpg&hl=en' target='_blank'>here</a>! We will announce when the iOS app is available as well.
|
||||||
|
p.small.muted by negue
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Seasonal Shop Tab
|
||||||
|
.seasonalshop_closed.pull-right
|
||||||
|
p Looks like a new tab has appeared under Inventory - the <a href='https://habitrpg.com/#/options/inventory/seasonalshop'>Seasonal Shop</a>! It's still closed, but I've heard a rumor that it will open soon...
|
||||||
|
p.small.muted by SabreCat and Lemoness
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Winter Plot-Line Continues
|
||||||
|
p Lemoness bursts into the Tavern, shaking icicles off her hat. "The Stoïkalm Steppes are completely abandoned!" she says, gulping the cup of tea that Daniel the Barkeep offers her. "No people milling about, no mounts and pets playing in the snow - and when I tried to fly closer, my dragon spooked and refused to land!"
|
||||||
|
p A cloaked figure in the corner steps into the fire light - SabreCat, a powerful adventurer from the north. "The Stoïkalm Steppes are the last home of many animals that have long since gone extinct elsewhere," he says. "The stoic Stoïkalmers would never flee their lands unless something was threatening their pets and mounts!"
|
||||||
|
p He turns to Lemoness. "I can speak the language of the northern beasts. I'll try to contact the roaming sabertooth prides to see if they know what happened." As he lopes off into the distance, a cold wind begins to blow.
|
||||||
|
p.muted Missed the first part of the Winter Plot-Line? Read it <a href='http://habitrpg.wikia.com/wiki/Whats_new'>here</a>.
|
||||||
|
h5 12/9/2014 - Penguin Pet Quest and Winter Plot-Line
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Penguin Pet Quest
|
||||||
|
.quest_penguin.pull-right
|
||||||
|
p Habiticans wanted to go ice-skating, but instead, a giant penguin is freezing everything in sight! All we wanted was to go ice-skating... Can you get this penguin to chill out? If so, you'll be rewarded with some penguins of your own!
|
||||||
|
p.small.muted by Melynnrose, Breadstrings, Rattify, Painter de Cluster, Daniel the Bard, and Leephon
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
h5 Winter Plot-Line
|
||||||
|
p Lemoness enters the Tavern with worrying news from the far north of Habitica. "Nobody's heard from the Stoïkalm Steppes for over a week," she says. "It's hard to imagine anything troubling the citizens there, since it's such a placid part of the continent... But just in case, maybe I should pay a visit." Sounds like a good plan to us!
|
||||||
|
|
||||||
h5 12/3/2014 - Gifting Subscriptions And Gems, New Subscription Benefits, Mysterious Time Travelers, Steampunk Item Sets, And Block Subscriptions!
|
h5 12/3/2014 - Gifting Subscriptions And Gems, New Subscription Benefits, Mysterious Time Travelers, Steampunk Item Sets, And Block Subscriptions!
|
||||||
table.table.table-striped
|
table.table.table-striped
|
||||||
tr
|
tr
|
||||||
|
|
|
||||||
|
|
@ -187,3 +187,9 @@ div(ng-if='::profile.achievements.costumeContest')
|
||||||
=env.t('costumeContestText')
|
=env.t('costumeContestText')
|
||||||
hr
|
hr
|
||||||
|
|
||||||
|
div(ng-if='::profile.achievements.nye')
|
||||||
|
.achievement.achievement-nye
|
||||||
|
h5=env.t('auldAcquaintance')
|
||||||
|
small
|
||||||
|
=env.t('auldAcquaintanceText', {cards: "{{::profile.achievements.nye}}"})
|
||||||
|
hr
|
||||||
|
|
|
||||||
|
|
@ -103,14 +103,14 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
|
||||||
span.task-notes
|
span.task-notes
|
||||||
span.glyphicon.glyphicon-comment
|
span.glyphicon.glyphicon-comment
|
||||||
//left-hand size commands
|
//left-hand size commands
|
||||||
.task-controls
|
.task-controls.task-primary
|
||||||
a.money.btn-buy.item-btn(ng-click='castStart(Content.spells.special.#{k})')
|
a.money.btn-buy.item-btn(ng-click='castStart(Content.spells.special.#{k})')
|
||||||
if canceler
|
if canceler
|
||||||
span.reward-cost {{Content.spells.special.#{k}.value}}
|
|
||||||
span.shop_gold
|
span.shop_gold
|
||||||
|
span.reward-cost {{Content.spells.special.#{k}.value}}
|
||||||
else
|
else
|
||||||
|
span.shop_spell(class='shop_#{k}')
|
||||||
span.reward-cost {{user.items.special.#{k}}}
|
span.reward-cost {{user.items.special.#{k}}}
|
||||||
span(class='shop_#{k}',style='display:inline-block;vertical-align:top;')
|
|
||||||
// main content
|
// main content
|
||||||
p.task-text {{Content.spells.special.#{k}.text()}}
|
p.task-text {{Content.spells.special.#{k}.text()}}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
|
||||||
|
|
||||||
// Due Date
|
// Due Date
|
||||||
span(ng-if='task.type=="todo" && task.date')
|
span(ng-if='task.type=="todo" && task.date')
|
||||||
span(ng-class='{"label label-danger":(moment(task.date).isBefore(_today, "days") && !task.completed)}') {{moment(task.date).format('MM/DD')}}
|
span(ng-class='{"label label-danger":(moment(task.date).isBefore(_today, "days") && !task.completed)}') {{task.date | date:user.preferences.dateFormat.substr(0,5)}}
|
||||||
|
|
||||||
// Streak
|
// Streak
|
||||||
|
|
|
|
||||||
span(ng-show='task.streak') {{task.streak}}
|
span(ng-show='task.streak') {{task.streak}}
|
||||||
|
|
@ -82,7 +83,7 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
|
||||||
//-| {{task.text}}
|
//-| {{task.text}}
|
||||||
|
|
||||||
div(ng-if='task.checklist && !$state.includes("options.social.challenges") && !task.collapseChecklist && !task._editing')
|
div(ng-if='task.checklist && !$state.includes("options.social.challenges") && !task.collapseChecklist && !task._editing')
|
||||||
fieldset.option-group
|
fieldset.option-group.task-checklist
|
||||||
label.checkbox(ng-repeat='item in task.checklist')
|
label.checkbox(ng-repeat='item in task.checklist')
|
||||||
input(type='checkbox',ng-model='item.completed',ng-change='saveTask(task,true)')
|
input(type='checkbox',ng-model='item.completed',ng-change='saveTask(task,true)')
|
||||||
markdown(ng-model='item.text',target='_blank')
|
markdown(ng-model='item.text',target='_blank')
|
||||||
|
|
@ -122,25 +123,24 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
|
||||||
a(ng-click="unlink(task, 'remove-all')")=env.t('removeThem')
|
a(ng-click="unlink(task, 'remove-all')")=env.t('removeThem')
|
||||||
|
|
||||||
// Checklists
|
// Checklists
|
||||||
div(ng-if='!$state.includes("options.social.challenges")')
|
.task-checklist-edit(ng-if='!$state.includes("options.social.challenges")')
|
||||||
button.btn.btn-default.btn-sm(ng-if='!task.checklist[0] && (task.type=="daily" || task.type=="todo")',ng-click='addChecklist(task)')
|
ul
|
||||||
span.glyphicon.glyphicon-tasks
|
li
|
||||||
|
|
button(type='button', ng-if='!task.checklist[0] && (task.type=="daily" || task.type=="todo")',ng-click='addChecklist(task)')
|
||||||
=env.t('addChecklist')
|
span.glyphicon.glyphicon-tasks
|
||||||
|
span=env.t('addChecklist')
|
||||||
form.checklist-form(ng-if='task.checklist')
|
form.checklist-form(ng-if='task.checklist')
|
||||||
fieldset.option-group.task-checklist(ng-if='!$state.includes("options.social.challenges")')
|
fieldset.option-group(ng-if='!$state.includes("options.social.challenges")')
|
||||||
legend.option-title
|
legend.option-title
|
||||||
span.hint(popover=env.t('checklistText'),popover-trigger='mouseenter',popover-placement='bottom')=env.t('checklist')
|
span.hint(popover=env.t('checklistText'),popover-trigger='mouseenter',popover-placement='bottom')=env.t('checklist')
|
||||||
ul(hrpg-sort-checklist).list-unstyled
|
ul(hrpg-sort-checklist)
|
||||||
li(ng-repeat='item in task.checklist')
|
li(ng-repeat='item in task.checklist')
|
||||||
a.pull-right.checklist-icons(ng-click='removeChecklistItem(task,$event,$index,true)')
|
//input(type='checkbox',ng-model='item.completed',ng-change='saveTask(task,true)')
|
||||||
span.glyphicon.glyphicon-trash(tooltip=env.t('delete'))
|
|
||||||
span.checklist-icons.glyphicon.glyphicon-resize-vertical()
|
|
||||||
|
|
|
||||||
input(type='checkbox',ng-model='item.completed',ng-change='saveTask(task,true)')
|
|
||||||
//-,ng-blur='saveTask(task,true)')
|
//-,ng-blur='saveTask(task,true)')
|
||||||
input.inline-edit(type='text',ng-model='item.text',ui-keydown="{'8 46':'removeChecklistItem(task,$event,$index)'}",ui-keyup="{'13':'addChecklistItem(task,$event,$index)','38 40':'navigateChecklist(task,$index,$event)'}")
|
span.checklist-icon.glyphicon.glyphicon-resize-vertical()
|
||||||
|
input(type='text',ng-model='item.text',ui-keydown="{'8 46':'removeChecklistItem(task,$event,$index)'}",ui-keyup="{'13':'addChecklistItem(task,$event,$index)','38 40':'navigateChecklist(task,$index,$event)'}")
|
||||||
|
a(ng-click='removeChecklistItem(task,$event,$index,true)')
|
||||||
|
span.glyphicon.glyphicon-trash(tooltip=env.t('delete'))
|
||||||
|
|
||||||
form(ng-submit='saveTask(task,false,true)')
|
form(ng-submit='saveTask(task,false,true)')
|
||||||
// text & notes
|
// text & notes
|
||||||
|
|
@ -152,27 +152,34 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
|
||||||
textarea.option-content(rows='3', ng-model='task.notes', ng-model-options="{debounce: 1000}")
|
textarea.option-content(rows='3', ng-model='task.notes', ng-model-options="{debounce: 1000}")
|
||||||
|
|
||||||
// if Habit, plus/minus command options
|
// if Habit, plus/minus command options
|
||||||
fieldset.option-group(ng-if='task.type=="habit" && !task.challenge.id')
|
fieldset.option-group.plusminus(ng-if='task.type=="habit" && !task.challenge.id')
|
||||||
legend.option-title=env.t('direction/Actions')
|
legend.option-title=env.t('direction/Actions')
|
||||||
span.task-checker.action-plusminus.select-toggle
|
span.task-checker
|
||||||
input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-plus', type='checkbox', ng-model='task.up')
|
input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-plus', type='checkbox', ng-model='task.up')
|
||||||
label(for='{{obj._id}}_{{task.id}}-option-plus')
|
label(for='{{obj._id}}_{{task.id}}-option-plus')
|
||||||
span.task-checker.action-plusminus.select-toggle
|
span.task-checker
|
||||||
input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-minus', type='checkbox', ng-model='task.down')
|
input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-minus', type='checkbox', ng-model='task.down')
|
||||||
label(for='{{obj._id}}_{{task.id}}-option-minus')
|
label(for='{{obj._id}}_{{task.id}}-option-minus')
|
||||||
|
|
||||||
// if Daily, calendar
|
// if Daily, calendar
|
||||||
fieldset(ng-if='::task.type=="daily"', class="option-group")
|
fieldset(ng-if='::task.type=="daily"', class="option-group")
|
||||||
legend.option-title=env.t('repeat')
|
legend.option-title=env.t('repeat')
|
||||||
.task-controls.tile-group.repeat-days(bindonce)
|
ul.repeat-days(bindonce)
|
||||||
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
|
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.su}', type='button', ng-click='task.challenge.id || (task.repeat.su = !task.repeat.su)') {{::moment.weekdaysMin(0)}}
|
li
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.m}', type='button', ng-click='task.challenge.id || (task.repeat.m = !task.repeat.m)') {{::moment.weekdaysMin(1)}}
|
button(ng-class='{active: task.repeat.su}', type='button', ng-click='task.challenge.id || (task.repeat.su = !task.repeat.su)') {{::moment.weekdaysMin(0)}}
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.t}', type='button', ng-click='task.challenge.id || (task.repeat.t = !task.repeat.t)') {{::moment.weekdaysMin(2)}}
|
li
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.w}', type='button', ng-click='task.challenge.id || (task.repeat.w = !task.repeat.w)') {{::moment.weekdaysMin(3)}}
|
button(ng-class='{active: task.repeat.m}', type='button', ng-click='task.challenge.id || (task.repeat.m = !task.repeat.m)') {{::moment.weekdaysMin(1)}}
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.th}', type='button', ng-click='task.challenge.id || (task.repeat.th = !task.repeat.th)') {{::moment.weekdaysMin(4)}}
|
li
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.f}', type='button', ng-click='task.challenge.id || (task.repeat.f= !task.repeat.f)') {{::moment.weekdaysMin(5)}}
|
button(ng-class='{active: task.repeat.t}', type='button', ng-click='task.challenge.id || (task.repeat.t = !task.repeat.t)') {{::moment.weekdaysMin(2)}}
|
||||||
button.task-action-btn.tile(ng-class='{active: task.repeat.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)') {{::moment.weekdaysMin(6)}}
|
li
|
||||||
|
button(ng-class='{active: task.repeat.w}', type='button', ng-click='task.challenge.id || (task.repeat.w = !task.repeat.w)') {{::moment.weekdaysMin(3)}}
|
||||||
|
li
|
||||||
|
button(ng-class='{active: task.repeat.th}', type='button', ng-click='task.challenge.id || (task.repeat.th = !task.repeat.th)') {{::moment.weekdaysMin(4)}}
|
||||||
|
li
|
||||||
|
button(ng-class='{active: task.repeat.f}', type='button', ng-click='task.challenge.id || (task.repeat.f= !task.repeat.f)') {{::moment.weekdaysMin(5)}}
|
||||||
|
li
|
||||||
|
button(ng-class='{active: task.repeat.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)') {{::moment.weekdaysMin(6)}}
|
||||||
|
|
||||||
// if Reward, pricing
|
// if Reward, pricing
|
||||||
fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')
|
fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')
|
||||||
|
|
@ -184,7 +191,7 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
|
||||||
// if Todos, the due date
|
// if Todos, the due date
|
||||||
fieldset.option-group(ng-if='task.type=="todo" && !task.challenge.id')
|
fieldset.option-group(ng-if='task.type=="todo" && !task.challenge.id')
|
||||||
legend.option-title=env.t('dueDate')
|
legend.option-title=env.t('dueDate')
|
||||||
input.option-content.datepicker(type='text', datepicker-popup='MM/dd/yyyy', ng-model='task.date', is-open='datepickerOpened', ng-click='datepickerOpened = true')
|
input.option-content.datepicker(type='text', datepicker-popup='{{user.preferences.dateFormat}}', ng-model='task.date', is-open='datepickerOpened', ng-click='datepickerOpened = true')
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
fieldset.option-group(ng-if='!$state.includes("options.social.challenges")')
|
fieldset.option-group(ng-if='!$state.includes("options.social.challenges")')
|
||||||
|
|
@ -199,10 +206,13 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
|
||||||
fieldset.option-group.advanced-option(ng-class="{visuallyhidden: task._advanced}")
|
fieldset.option-group.advanced-option(ng-class="{visuallyhidden: task._advanced}")
|
||||||
legend.option-title
|
legend.option-title
|
||||||
a.hint.priority-multiplier-help(href='https://trello.com/card/priority-multiplier/50e5d3684fe3a7266b0036d6/17', target='_blank', popover-title=env.t('difficultyHelpTitle'), popover-trigger='mouseenter', popover=env.t('difficultyHelpContent'))=env.t('difficulty')
|
a.hint.priority-multiplier-help(href='https://trello.com/card/priority-multiplier/50e5d3684fe3a7266b0036d6/17', target='_blank', popover-title=env.t('difficultyHelpTitle'), popover-trigger='mouseenter', popover=env.t('difficultyHelpContent'))=env.t('difficulty')
|
||||||
.task-controls.tile-group.priority-multiplier
|
ul.priority-multiplier
|
||||||
button.task-action-btn.tile(type='button', ng-class='{active: task.priority==1 || !task.priority}', ng-click='task.challenge.id || (task.priority=1)')=env.t('easy')
|
li
|
||||||
button.task-action-btn.tile(type='button', ng-class='{active: task.priority==1.5}', ng-click='task.challenge.id || (task.priority=1.5)')=env.t('medium')
|
button(type='button', ng-class='{active: task.priority==1 || !task.priority}', ng-click='task.challenge.id || (task.priority=1)')=env.t('easy')
|
||||||
button.task-action-btn.tile(type='button', ng-class='{active: task.priority==2}', ng-click='task.challenge.id || (task.priority=2)')=env.t('hard')
|
li
|
||||||
|
button(type='button', ng-class='{active: task.priority==1.5}', ng-click='task.challenge.id || (task.priority=1.5)')=env.t('medium')
|
||||||
|
li
|
||||||
|
button(type='button', ng-class='{active: task.priority==2}', ng-click='task.challenge.id || (task.priority=2)')=env.t('hard')
|
||||||
//span(ng-if='task.type=="daily" && !task.challenge.id')
|
//span(ng-if='task.type=="daily" && !task.challenge.id')
|
||||||
span(ng-if='task.type=="daily"')
|
span(ng-if='task.type=="daily"')
|
||||||
legend.option-title.pull-left=env.t('restoreStreak')
|
legend.option-title.pull-left=env.t('restoreStreak')
|
||||||
|
|
@ -210,12 +220,17 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
|
||||||
|
|
||||||
div(ng-if='(user.preferences.allocationMode == "taskbased" && user.preferences.automaticAllocation) || $state.is("options.social.challenges")')
|
div(ng-if='(user.preferences.allocationMode == "taskbased" && user.preferences.automaticAllocation) || $state.is("options.social.challenges")')
|
||||||
legend.option-title.pull-left=env.t('attributes')
|
legend.option-title.pull-left=env.t('attributes')
|
||||||
.task-controls.tile-group.task-attributes
|
ul.task-attributes
|
||||||
button.task-action-btn.tile(type='button', ng-class='{active: task.attribute=="str"}', ng-click='task.attribute="str"')=env.t('physical')
|
li
|
||||||
button.task-action-btn.tile(type='button', ng-class='{active: task.attribute=="int"}', ng-click='task.attribute="int"')=env.t('mental')
|
button(type='button', ng-class='{active: task.attribute=="str"}', ng-click='task.attribute="str"')=env.t('physical')
|
||||||
button.task-action-btn.tile(type='button', ng-class='{active: task.attribute=="con"}', ng-click='task.attribute="con"')=env.t('social')
|
li
|
||||||
button.task-action-btn.tile(type='button', ng-class='{active: task.attribute=="per"}', ng-click='task.attribute="per"', popover=env.t('otherExamples'), popover-trigger='mouseenter', popover-placement='top')=env.t('other')
|
button(type='button', ng-class='{active: task.attribute=="int"}', ng-click='task.attribute="int"')=env.t('mental')
|
||||||
|
li
|
||||||
|
button(type='button', ng-class='{active: task.attribute=="con"}', ng-click='task.attribute="con"')=env.t('social')
|
||||||
|
li
|
||||||
|
button(type='button', ng-class='{active: task.attribute=="per"}', ng-click='task.attribute="per"', popover=env.t('otherExamples'), popover-trigger='mouseenter', popover-placement='top')=env.t('other')
|
||||||
|
|
||||||
button.task-action-btn.tile.spacious(type='submit')=env.t('saveAndClose')
|
.save-close
|
||||||
|
button(type='submit')=env.t('saveAndClose')
|
||||||
|
|
||||||
div(class='{{obj._id}}{{task.id}}-chart', ng-show='charts[obj._id+task.id]')
|
div(class='{{obj._id}}{{task.id}}-chart', ng-show='charts[obj._id+task.id]')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue