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

This commit is contained in:
deilann
2014-02-15 06:46:12 -08:00
121 changed files with 3321 additions and 2865 deletions
+1 -6
View File
@@ -1,8 +1,3 @@
{
"directory": "public/bower_components",
"storage": {
"packages": ".bower-cache",
"registry": ".bower-registry"
},
"tmp": ".bower-tmp"
"directory": "public/bower_components"
}
+2
View File
@@ -12,6 +12,8 @@ newrelic_agent.log
.bower-tmp
.bower-registry
.bower-cache
*.log
src/*/*.map
src/*/*/*.map
test/*.js
+2
View File
@@ -6,4 +6,6 @@ Gruntfile.js
CHANGELOG.md
.idea*
.git*
*.log
newrelic_agent.log
.bower-cache*
+28 -13
View File
@@ -4,6 +4,8 @@ module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
git_changelog: {
minimal: {
options: {
@@ -56,7 +58,10 @@ module.exports = function(grunt) {
build: {
files: [
{expand: true, cwd: 'public/', src: 'favicon.ico', dest: 'build/'},
{expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/dist/spritesmith-*.png', dest: 'build/'}
{expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/dist/spritesmith.png', dest: 'build/'},
{expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/img/sprites/backer-only/*.gif', dest: 'build/'},
{expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/img/sprites/npc_ian.gif', dest: 'build/'},
{expand: true, cwd: 'public/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'build/'}
]
}
},
@@ -69,16 +74,18 @@ module.exports = function(grunt) {
},
src: [
'build/*.js', 'build/*.css', 'build/favicon.ico',
'build/bower_components/bootstrap/docs/assets/css/*.css',
'build/bower_components/habitrpg-shared/dist/*.css'
'build/bower_components/habitrpg-shared/dist/*.png',
'build/bower_components/habitrpg-shared/img/sprites/backer-only/*.gif',
'build/bower_components/habitrpg-shared/img/sprites/npc_ian.gif',
'build/bower_components/bootstrap/dist/fonts/*'
],
dest: 'make-sure-i-do-not-exist'
dest: 'build/*.css'
}
},
nodemon: {
nodemon: {
dev: {
ignoredFiles: ['public/*', 'Gruntfile.js', 'CHANGELOG.md', 'views/*', 'build/*', '.idea*', '.git*']
script: '<%= pkg.main %>'
}
},
@@ -106,27 +113,35 @@ module.exports = function(grunt) {
var files = grunt.file.readJSON('./public/manifest.json');
var uglify = {};
var cssmin = {};
_.each(files, function(val, key){
var js = uglify['build/' + key + '.js'] = [];
_.each(files[key]['js'], function(val){
js.push('public/' + val);
});
var css = cssmin['build/' + key + '.css'] = [];
_.each(files[key]['css'], function(val){
if(val == 'app.css' || val == 'static.css'){
cssmin['build/' + val] = ['build/' + val]
}else{
cssmin['build/' + val] = ['public/' + val]
}
var path = (val == 'app.css' || val == 'static.css') ? 'build/' : 'public/';
css.push(path + val)
});
});
grunt.config.set('uglify.build.files', uglify);
grunt.config.set('uglify.build.options', {compress: false})
grunt.config.set('uglify.build.options', {compress: false});
grunt.config.set('cssmin.build.files', cssmin);
// Rewrite urls to relative path
grunt.config.set('cssmin.build.options', {'target': 'public/css/whatever-css.css'});
});
// Register tasks.
grunt.registerTask('build:prod', ['loadManifestFiles', 'clean:build', 'uglify', 'stylus', 'cssmin', 'copy:build', 'hashres']);
grunt.registerTask('build:dev', ['loadManifestFiles', 'clean:build', 'stylus', 'cssmin', 'copy:build']);
grunt.registerTask('build:dev', ['stylus']);
grunt.registerTask('run:dev', [ 'build:dev', 'concurrent' ]);
+13 -15
View File
@@ -14,20 +14,21 @@
"tests"
],
"dependencies": {
"jquery": "~2.0.3",
"jquery": "~2.1.0",
"jquery.cookie": "~1.4.0",
"jquery-ui": "~1.10.3",
"angular": "~1.2.1",
"angular-sanitize": "~1.2.1",
"angular-resource": "~1.2.1",
"angular-ui": "~0.4.0",
"angular-bootstrap": "~0.5.0",
"angular": "~1.2.12",
"angular-sanitize": "~1.2.12",
"angular-resource": "~1.2.12",
"angular-ui-utils": "~0.1.0",
"angular-ui-select2": "https://github.com/angular-ui/ui-select2.git#4469fd7a32eba2e28d5e3dd319b49e1ad090b167",
"angular-bootstrap": "~0.10.0",
"angular-ui-router": "ca3b4777a603df8f86cfd653c8f6c38b2ae05d89",
"angular-loading-bar": "~0.0.5",
"angular-loading-bar": "~0.3.0",
"angular-bindonce": "~0.2.1",
"bootstrap": "v2.3.2",
"bootstrap-datepicker": "~1.2.0",
"bootstrap-growl": "~1.1.0",
"bootstrap": "~3.1.0",
"bootstrap-growl": "git://github.com/ifightcrime/bootstrap-growl.git#master",
"bootstrap-tour": "~0.8.1",
"habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared.git#develop",
"BrowserQuest": "https://github.com/mozilla/BrowserQuest.git",
"github-buttons": "git://github.com/mdo/github-buttons.git",
@@ -35,15 +36,12 @@
"JavaScriptButtons": "git://github.com/paypal/JavaScriptButtons.git#master",
"Angular-At-Directive": "git://github.com/snicker/Angular-At-Directive#master",
"js-emoji": "git://github.com/snicker/js-emoji#master",
"gemoji": "git://github.com/github/gemoji",
"sticky": "*",
"bootstrap-tour": "~0.8.0",
"angular-ui-utils": "~0.1.0",
"swagger-ui": "https://github.com/wordnik/swagger-ui.git#105c516f5f055b140e935e9cfe0c36f841921dff",
"ngInfiniteScroll": "~1.0.0",
"angular-ui-select2": "https://github.com/angular-ui/ui-select2.git#4469fd7a32eba2e28d5e3dd319b49e1ad090b167"
"jquery-colorbox": "~1.4.36"
},
"devDependencies": {
"angular-mocks": "~1.2.1"
"angular-mocks": "~1.2.12"
}
}
+5 -1
View File
@@ -11,8 +11,12 @@
"SMTP_USER":"user@domain.com",
"SMTP_PASS":"password",
"SMTP_SERVICE":"Gmail",
"SMTP_HOST":"smtp.gmail.com",
"SMTP_PORT": 587,
"SMTP_TLS": true,
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
"PAYPAL_MERCHANT":"paypal-merchant@gmail.com",
"NEW_RELIC_LICENSE_KEY":"NEW_RELIC_LICENSE_KEY"
"NEW_RELIC_LICENSE_KEY":"NEW_RELIC_LICENSE_KEY",
"GA_ID": "GA_ID"
}
+23
View File
@@ -0,0 +1,23 @@
// Give contrib.level 7+ free subscription for life
db.users.update(
{
'contributor.level':{$gte:7},
'purchased.plan.customerId':null
},
{
$set: {
'purchased.plan':{
planId: 'basic',
customerId: 'habitrpg',
dateCreated: new Date,
dateUpdated: new Date,
gemsBought: 0
}
}
},
{multi:true}
)
+1 -1
View File
@@ -1 +1 @@
db.users.update({_id:''},{$set:{'purchased.ads':true},$inc:{balance:5}});
db.users.update({_id:''},{$inc:{balance:5}});
+6 -2
View File
@@ -40,12 +40,16 @@
"pretty-data": "git://github.com/vkiryukhin/pretty-data#master",
"js2xmlparser": "~0.1.2",
"mongoose": "~3.8.1",
"domain-middleware": "~0.1.0",
"swagger-node-express": "git://github.com/lefnire/swagger-node-express#habitrpg",
"passport": "~0.1.18",
"passport-facebook": "~1.0.2",
"newrelic": "~1.3.0",
"connect-ratelimit": "0.0.6"
"connect-ratelimit": "0.0.6",
"winston": "~0.7.2",
"winston-mail": "~0.2.7",
"winston-newrelic": "~0.1.4",
"domain-middleware": "~0.1.0",
"universal-analytics": "~0.3.2"
},
"private": true,
"subdomain": "habitrpg",
+21 -24
View File
@@ -16,6 +16,27 @@
.wide-popover
max-width: 400px
// Small alerts
alert-sm-mixin()
padding-top: 8px
padding-bottom: 8px
.alert-sm
alert-sm-mixin()
#notification-area
position: fixed
top: 0
right: 0px
z-index: 1061
.alert
alert-sm-mixin()
.notification-icon
transform: scale(0.8)
// variables
gpColor = #FBEFC0
@@ -78,30 +99,6 @@ borderDarken = 20%
border-color: darken(dropColor,borderDarken)
color: dropText
// alert icons
.icon-gold
background: url("/bower_components/habitrpg-shared/img/coin_single_gold.png") no-repeat
background-position: center center
background-size: 18px
width: 14px
height: 14px
.icon-silver
background: url("/bower_components/habitrpg-shared/img/coin_single_silver.png") no-repeat
background-position: center center
background-size: 18px
width: 14px
height: 14px
.icon-death
background: url("/bower_components/habitrpg-shared/img/sprites/dead.png") no-repeat
background-position: center center
background-size: 14px
width: 14px
height: 14px
.undo-button
z-index: 3000
position:absolute
+21 -22
View File
@@ -9,6 +9,16 @@ Seeing as the sprites might change drastically in the
future re: pets and whatnot, this is just temporary.
---------------------------------------------------- */
.profile-modal-header
width:560px
margin: 0px auto
.avatar-level
position absolute
bottom 0px
right 0px
padding 2px 3px 2px 3px
// basic herobox styles
.herobox
height: 10.5em // higher to acct for the name area
@@ -40,7 +50,7 @@ future re: pets and whatnot, this is just temporary.
// always visible on the user's own avatar
.herobox:after
opacity: 0
.herobox.isUser:after
.herobox.isUser:not(.hasMount):after
opacity: 1
// make it a bit special
background darken($better, 15%)
@@ -52,21 +62,17 @@ future re: pets and whatnot, this is just temporary.
.active
border 1px solid black
// vertically center avatar sprite
// depending on if they have a pet
.herobox.hasPet
padding-top: 2em
.herobox:not(.hasPet)
padding-top: 1.75em
// if it is the user's avatar, check if
// they have a pet and pad accordingly
.herobox.hasPet.isUser
padding-top: 3.25em
// if not user's avatar, remove lines for party unity
.herobox:not(.isUser)
outline: 0
outline: 0
// vertically center avatar sprite depending on if they have a pet
.herobox.hasPet
padding-top: 1.75em
.herobox:not(.hasPet)
padding-top: 2em
.herobox.hasMount
padding-top:0em
// expose hero info on hover, taking
// into account the extra pet space
@@ -74,13 +80,6 @@ future re: pets and whatnot, this is just temporary.
background: desaturate(lighten($better, 30%), 10%)
&:after
opacity: 1
/* .herobox.hasPet
&:hover, &:focus
padding-top: 3.25em
.herobox:not(.hasPet)
&:hover, &:focus
padding-top: 2.5em */
// positioning the sprites, etc
.herobox
@@ -93,4 +92,4 @@ future re: pets and whatnot, this is just temporary.
position: absolute
.herobox.isLeader
background: $better
background: $better
+1 -9
View File
@@ -10,12 +10,4 @@ ul.challenge-accordion-header-specs
margin-bottom: 10px
#challenges-filters h3
margin-top: 0px;
// for things like challenges, member profiles, etc
@media (min-width: 767px)
.wide-modal
/* new custom width */
width: 1020px;
/* must be half of the width, minus scrollbar on the left (30px) */
margin-left: -480px;
margin-top: 0px;
+6 -3
View File
@@ -22,15 +22,18 @@ menu
line-height: 2
.customize-option
border: 0px solid grey;
background-color: hsl(0, 0%, 93%);
border: 0px solid grey
background-color: hsl(0, 0%, 93%)
margin-bottom: 10px
cursor: pointer;
-webkit-transition: background-color 0.5s ease-out
-moz-transition: background-color 0.5s ease-out
transition: background-color 0.5s ease-out
&.hair
width: 40px !important
height: 40px !important
.customize-option:active
background-color: rgb(255, 242, 204);
-webkit-transition: none
+22 -33
View File
@@ -1,36 +1,25 @@
/* Filters */
.filters
padding-left: 19px
padding-right: 19px
.filters .nav
margin-bottom: 0
.filter-description
margin-top: 7px
margin-right: 7px
/* Filters applied to tasks */
.task.filtered-out
display: none
.tag-label:hover
background-color: #3a87ad
cursor: pointer
.tag-editing
padding 2px 0 0 2px
.tag-editing .tag-editing-pill
height:auto
margin-top: 5px
margin-bottom: 5px
.tag-list
overflow-y: auto
white-space: nowrap
.nav-pills
height: 43px
li
display: inline-block
float: none
.filter-description
margin-top: 7px
margin-right: 7px
padding-left: 7px
markdown
display: inline-block
.glyphicon-bullhorn
margin-right: 5px
.input-group
float: left
width: 1px // Trick for proper alignment
margin-left: 3px
margin-right: 3px
margin-top: 5px
.form-control
width: auto
+6 -80
View File
@@ -1,83 +1,9 @@
/* Sticky footer styles
-------------------------------------------------- */
html,
body {
height: 100%;
/* The html and body elements cannot have any padding or margin. */
}
/* Wrapper for page content to push down footer */
#wrap {
min-height: 100%;
height: auto !important;
height: 100%;
/* Negative indent footer by it's height */
margin: 0 auto -60px;
}
/* Set the fixed height of the footer here */
#push,
#footer {
height: 60px;
}
#footer {
background-color: #f5f5f5;
}
/* Lastly, apply responsive CSS fixes as necessary */
@media (max-width: 767px) {
#footer {
margin-left: -20px;
margin-right: -20px;
padding-left: 20px;
padding-right: 20px;
}
}
/* Custom page CSS
-------------------------------------------------- */
/* Not required for template or sticky footer method. */
#wrap > .container {
padding-top: 60px;
}
.container .credit {
margin: 20px 0;
}
code {
font-size: 80%;
}
/* HabitRPG Custom CSS
-------------------------------------------------- */
.footer {
/*padding: 70px 0;*/
border-top: 1px solid #e5e5e5;
background-color: #f5f5f5;
}
.footer li {
text-align: left;
}
.footer h4 {
text-align: left;
}
.footer td {
align: left !important;
}
.frontpage-play-button
//box-shadow: 0 0 40px #494141;
display:inline
font-size: 20px
width:100px
height:50px
.footer
padding-top: 20px
margin-top: 40px
padding-bottom: 20px
border-top: 1px solid #e5e5e5
background-color: #f5f5f5
+48 -40
View File
@@ -1,6 +1,3 @@
.full-width
width 100%
.border-right
border-right 1px solid #ddd
@@ -8,21 +5,27 @@
color #fff
background-color #51a351
.tavern-pane
position relative
height 250px
.chat-form
position: relative
z-index: 10
margin-bottom: 10px
.chat-message .chat-plus-one
opacity: 0
.chat-message:hover .chat-plus-one
opacity: 1
textarea
margin-bottom: 5px
.chat-message
markdown, span.time
padding: 0 3px
.chat-plus-one
opacity: 0
background-color: #eee
padding: 3px 3px 0px 3px
&:hover .chat-plus-one
opacity: 1
color: #999
.tavern-chat, .party-chat
li
padding-top:15px
padding-bottom:15px
@@ -78,6 +81,11 @@
padding-top:0px
padding-bottom:0px
.panel-tiers
div
display: none
margin-top: 10px
// Name tags
.label-contributor-1, .label-contributor-2
background-color: #333;
@@ -108,33 +116,33 @@
padding: 0px 5px
// Autocomplete [TODO] make this nicer
.list-at-user {
width: 100%;
max-width: 120px;
position: absolute;
background: white;
border: 1px solid #a4a4a4;
z-index: 10;
}
.list-at-user
width: 100%
max-width: 120px
position: absolute
background: white
border: 1px solid #a4a4a4
z-index: 10
.list-at-user li {
line-height:14px;
border-bottom: 1px solid #a4a4a4;
list-style-type: none;
}
.list-at-user li:hover {
cursor: pointer;
background: #b9dff4;
}
.list-at-user li
line-height:14px
border-bottom: 1px solid #a4a4a4
list-style-type: none
.list-at-user li span {
margin: 2px;
font-size: 11.844px;
margin-left: 5px;
display: inline-block;
}
.list-cur {
background: #b9dff4;
}
.list-at-user li:hover
cursor: pointer
background: #b9dff4
.list-at-user li span
margin: 2px
font-size: 11.844px
margin-left: 5px
display: inline-block
.list-cur
background: #b9dff4
+78 -99
View File
@@ -3,9 +3,6 @@
// Vendor Includes - include first so we can override
// Import only styles that do not have urls to images! Include them directly in the page!
@import "../bower_components/bootstrap-datepicker/css/datepicker.css"
@import "../bower_components/angular-ui/build/angular-ui.min.css"
@import "../bower_components/bootstrap/docs/assets/css/bootstrap-responsive.css"
@import "../bower_components/bootstrap-tour/build/css/bootstrap-tour.min.css"
@import "../bower_components/angular-loading-bar/build/loading-bar.css"
@import "../bower_components/js-emoji/emoji.css"
@@ -19,7 +16,6 @@
@import "./inventory.styl"
@import "./alerts.styl"
@import "./helpers.styl"
@import "./responsive.styl"
@import "./header.styl"
@import "./filters.styl"
@import "./scrollbars.styl"
@@ -29,10 +25,7 @@
@import "./classes.styl"
@import "./quests.styl"
@import "./shared.styl"
// fix exploding to very wide for some reason
.datepicker
width: 210px
@import "./footer.styl"
html,body,p,h1,ul,li,table,tr,th,td
margin: 0
@@ -42,54 +35,27 @@ html,body,p,h1,ul,li,table,tr,th,td
-webkit-box-sizing: border-box
-moz-box-sizing: border-box
box-sizing: border-box
* { font-family: "Lato", sans-serif }
font-family: "Lato", sans-serif
hr
border-top: 0
border-bottom: 1px solid #ddd
border-color: rgba(0,0,0,0.1)
/* Footer
-------------------------------------------------- */
/* Bootstrap website boilerplate */
.footer {
padding: 70px 0;
border-top: 1px solid #e5e5e5;
background-color: #f5f5f5;
}
.footer p {
margin-bottom: 0;
color: #777;
}
.footer-links {
margin: 10px 0;
}
.footer-links li {
display: inline;
margin-right: 10px;
}
/* Bootstrap overrides */
div.tooltip-inner {
max-width: 300px;
}
/* Customizations to make footer sticky */
/*html, body {height: 100%;}*/
#wrap
min-height: 100%
z-index:-1
#main
padding-bottom: 250px; /* don't know why this works, sticky footers are weird */
@media (max-width: 600px) {
#wrap {
html, body
height: 100%
#wrap
z-index:-1
padding: 0 15px // Required by Bootstrap 3 when using .container-fluid
min-height: 100%
height: auto !important
@media (max-width: 600px)
#wrap
margin-top: 0
}
}
/* Gems
-------------------------------------------------- */
@@ -108,17 +74,17 @@ div.tooltip-inner {
position: relative;
.gem-cost::before
content: "";
display: block;
width: 0;
height: 0;
border: 6px solid #D4D4D4;
border-color: transparent #D4D4D4 transparent transparent;
position: absolute;
right: 100%;
margin-right: 0;
top: 50%;
margin-top: -6px;
content: ""
display: block
width: 0
height: 0
border: 6px solid #D4D4D4
border-color: transparent #D4D4D4 transparent transparent
position: absolute
right: 100%
margin-right: 0
top: 50%
margin-top: -6px
.gem-cost::after
content: "";
@@ -135,68 +101,81 @@ div.tooltip-inner {
/* Misc
-------------------------------------------------- */
//move
.modal
whitespace: normal
.new-stuff-modal // a .modal-body
h5
font-weight: 700
.notification-character
float:left
.modal-fixed-height
overflow-y: auto
max-height: 400px // Taken from Bootstrap 2.3.2 css, may need to change
.notification-message
display: inline-block;
padding-right: 1em
line-height: 3
.notification-action
vertical-align: baseline
#reset-modal
z-index:1500
#notification-area
position: fixed;
top: 0;
right: 0px;
z-index: 1061;
// notification-character used only by death modal's figure, move
.modal-body
margin: 10px
figure
float: left
padding: 15px
// TODO this is supposed to go in avatar.styl, but stylus is breaking if I even touch that file
.profile-modal-header
width:560px
margin: 0px auto
.avatar-level
position absolute
bottom 0px
right 0px
padding 1px 3px 1px 3px
.death-modal // a .modal-body
margin: 10px
a
cursor: pointer
#modalMember p
word-wrap: break-word
a span.glyphicon
color: #333 // Takes base color from bootstrap
a.label
color: #fff
.label .glyphicon
color: #fff
.muted
color: #999
.btn
margin-right: 5px
.vertical-align
vertical-align: middle
.inline-block
display: inline-block
// Buttons in groups should not have a 5px margin between them; suppress margin for buttons in groups.
.btn-group .btn
margin-right: 0px
.modal-indented-list
margin-left: 10px;
padding-left: 10px;
.inline-modal
position: relative
top: auto;
left: auto
right: auto
margin: 0 auto 20px
z-index: 1
max-width: 100%
.buy-gems
.nav-tabs
margin-top: 0.5em
.gem-wallet
padding-top: 2px
// Add badges' classes
.badge-info
background-color: #428bca
.badge-success
background-color: #47a447
.white, .white a
color: #fff !important
.collapse-toolbar
position:absolute
right:0px
top:0px
width:30px
height:25px
background-color:#222
z-index:999
.glyphicon
color:white
padding-left:7px
padding-top:5px
+11 -27
View File
@@ -15,29 +15,12 @@
.modal-header .gem-wallet
padding-top: 0px
.equipment-title
display: inline-block
vertical-align: middle
// pets (this will all change when pet system is overhauled)
.pet-grid
padding-top: 1.5em
border-top: rgba(0,0,0,0.1)
table
width: 100%
td
padding: 0.5em
//width: 25%
&.active-pet
background-color: $bad
outline: 1px solid rgba(0,0,0,0.1)
outline-offset: -1px
&:hover, &:focus
background-color: darken($better, 10%)
> div
margin:auto
margin-bottom:.5em
p
text-align:center
width:6.5em
height:2.5em
.stable .static-popover
max-width: 550px
menu.pets div
display: inline-block
@@ -57,12 +40,13 @@ menu.pets div
// display:block
.food-tray
position:fixed
right:0px
bottom:0px
width:30%
position: fixed
right: 15px
bottom: 0px
width: 30%
height: 50%
overflow-y: scroll
overflow-y: auto
z-index: 1
.inventory-list
p
+5 -5
View File
@@ -4,15 +4,15 @@
.npc_matt
margin-bottom: 20px
.toolbar-bailey-container
width:55px
position:relative
.npc_bailey
float:left
.npc_bailey_head
//height: 30px !important
height: 50px !important
position: absolute
//top: 117px // 147 (header-height) - 30 (bailey height)
top: 97px
right: 50px
top: 4px
height:45px !important
cursor: pointer
// Tour (Justin)
-61
View File
@@ -1,61 +0,0 @@
// widths
.full
width: 100%;
.half
width: 50%;
.third
width: 33.3333%;
.two-thirds
width: 66.6666%;
.quarter
width: 25%;
.three-quarters
width: 75%;
.fifth
width: 20%;
.two-fifths
width: 40%;
.three-fifths
width: 60%;
.four-fifths
width: 80%;
.sixth
width: 16.6667%;
.eighth
width: 12.5%;
.tenth
width: 10%;
.twelfth
width: 8.3333%;
.grid
padding: 0 1.5em 1.5em 0
letter-spacing: -4px
.module
display: inline-block
letter-spacing: normal
vertical-align: top
width: 25%
padding: 0 0 1.5em 1.5em
-moz-box-sizing: border-box
box-sizing: border-box
// at 960px when 4 columns really starts to break
@media (max-width: 60em)
.module
width: 50%
.task-column
max-height: 18em
overflow-y: scroll
// at 600px when 2col starts breaking
@media (max-width: 37.5em)
.grid
padding-right: 0
.module
width: 100%
padding-left: 0
.task-column
max-height: none
overflow: visible
+11 -1
View File
@@ -13,4 +13,14 @@
.dashed-underline
border-bottom:1px dashed #000
text-decoration:none
cursor pointer
cursor pointer
// Required by angular-ui-bootstrap
.nav, .pagination, .carousel, .panel-title a
cursor: pointer
.nav-tabs
margin-bottom: 15px
img
max-width: 100%
-21
View File
@@ -1,21 +0,0 @@
.jumbotron {
background-color: #1b1b1b;
background-image: -moz-linear-gradient(top, #222222, #111111);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));
background-image: -webkit-linear-gradient(top, #222222, #111111);
background-image: -o-linear-gradient(top, #222222, #111111);
background-image: linear-gradient(to bottom, #222222, #111111);
background-repeat: repeat-x;
border-color: #252525;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);
background:none;
color:black;
}
body {
padding-top:0px;
}
a {
cursor: pointer;
}
+31 -3
View File
@@ -3,10 +3,38 @@
// Vendor Includes - include first so we can override
// Import only styles that do not have urls to images! Include them directly in the page!
@import "../bower_components/angular-loading-bar/build/loading-bar.css"
@import "./static-pages.css"
@import "./shared.styl"
@import "./footer"
body
margin-top: 60px
#aboutPage
iframe
margin-bottom: 20px
.muted i, i.muted
opacity:0.5
opacity:0.5
#header-play-button
margin-right: 15px
#tagline
margin:10px 0 20px 0
font-size:40px
.marketing
text-align: center
#frontpage-play-button
//box-shadow: 0 0 40px #494141;
display:inline
font-size: 20px
width:100px
height:50px
#about-page
img
box-shadow 0px 0px 10px 5px #888
margin 10px
max-width 500px
+44 -14
View File
@@ -23,6 +23,8 @@ for $stage in $stages
.completed
background-color: $completed
color: #999
.glyphicon
color: #333
.task-checker label
background-color: darken($completed, 30%)
.task-action-btn
@@ -31,11 +33,20 @@ for $stage in $stages
.reward
background-color: white
// 2. Columns & Tasks
// ===================
.tasks-lists
.row
margin-left: -5px
margin-right: -5px
.col-md-3
padding-left: 10px // Override Bootstrap 15px padding
padding-right: 10px // Override Bootstrap 15px padding
margin-bottom: 15px
// main columns
// ------------
.task-column
@@ -47,7 +58,13 @@ for $stage in $stages
.task-column_title
margin: 0 0 0.5em
padding: 0
font-weight: 300
// 50% width columns with scrollbars for tablets
@media (min-width: 768px) and (max-width: 970px)
.task-column
max-height: 18.6em
overflow-y: scroll
// add new task form
// -----------------
@@ -112,6 +129,9 @@ for $stage in $stages
&:last-child
margin-bottom: 0
label
font-weight: 400
// task content
.task-text
display: block
@@ -124,19 +144,14 @@ for $stage in $stages
height:1.5em
background-size:1.5em
// task due date (for to-dos)
.task-date
font-size: 70%
.habit-wide .task-text
padding-left: 7em
// when a task is being dragged
.task.ui-sortable-helper {
.task.ui-sortable-helper
box-shadow: 0 0 3px rgba(0,0,0,0.15), 0 0 5px rgba(0,0,0,0.1)
transform: scale(1.05)
outline: 1px solid rgba(0,0,0,0.2)
}
// primary task commands
// ----------------------
@@ -270,13 +285,23 @@ for $stage in $stages
// -----------------------
.task-meta-controls
float: right
margin: 0.75em 0.5em 0 0.25em
margin: 0.75em 0.5em 0 0.5em
height: 1em
opacity: 0.25
a > i
margin-left: 0.125em
.task-notes
margin-left: 0.125em
a
text-decoration: none
// improve alignment of task icons
.glyphicon-signal
margin-left: 0.25em
.glyphicon-remove, .glyphicon-ok, a.badge
margin-right: 0.25em
.glyphicon-tags
margin-left: 0.1em
margin-right: 0.4em
a.badge
position: relative; top:-2px
.task:hover .task-meta-controls
opacity: 1
@@ -296,7 +321,6 @@ form
[class$="-options"]
padding: 1em 1em 0
color: #333
z-index: 0
position: relative
//background-color:
@@ -360,6 +384,9 @@ form
text-align: center
margin: 0
&.task-attributes
text-align: left
.task-action-btn.tile
border: 0
font-size: 1.15em
@@ -393,6 +420,9 @@ form
&:first-child
border-right: 0
.task-options
.task-action-btn.tile
margin: 3px
// todos ui
// --------
+33 -31
View File
@@ -1,23 +1,5 @@
"use strict";
window.env = window.env || {}; //FIX tests
if(window.env.language && window.env.language.momentLang && window.env.language.momentLangCode){
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.text = window.env.language.momentLang;
head.appendChild(script);
moment.lang(window.env.language.momentLangCode);
}
window.env.t = function(stringName, vars){
var string = window.env.translations[stringName];
if(!string) return window._.template(window.env.translations.stringNotFound, {string: stringName});
return vars === undefined ? string : window._.template(string, vars);
}
window.habitrpg = angular.module('habitrpg',
['ngResource', 'ngSanitize', 'userServices', 'groupServices', 'memberServices', 'challengeServices',
'authServices', 'notificationServices', 'guideServices', 'authCtrl',
@@ -132,8 +114,12 @@ window.habitrpg = angular.module('habitrpg',
.state('options.social.guilds.detail', {
url: '/:gid',
templateUrl: 'partials/options.social.guilds.detail.html',
controller: ['$scope', 'Groups', '$stateParams', function($scope, Groups, $stateParams){
$scope.group = Groups.Group.get({gid:$stateParams.gid});
controller: ['$scope', 'Groups', '$stateParams',
function($scope, Groups, $stateParams){
Groups.Group.get({gid:$stateParams.gid}, function(group){
$scope.group = group;
Groups.seenMessage(group._id);
});
}]
})
@@ -218,24 +204,40 @@ window.habitrpg = angular.module('habitrpg',
}
// Handle errors
var interceptor = ['$rootScope', '$q', function ($rootScope, $q) {
$httpProvider.responseInterceptors.push(['$rootScope', '$q', function ($rootScope, $q) {
function success(response) {
return response;
}
function error(response) {
//var status = response.status;
response.data = (response.data.err) ? response.data.err : response.data;
if (response.status == 0) response.data = 'Server currently unreachable.';
if (response.status == 500) response.data += ' (see Chrome console for more details).';
debugger;
// Offline
if (response.status == 0 ||
// don't know why we're getting 404 here, should be 0
(response.status == 404 && _.isEmpty(response.data))) {
$rootScope.$broadcast('responseText', window.env.t('serverUnreach'));
var error = response.status == 0 ? response.data : ('Error ' + response.status + ': ' + response.data);
$rootScope.$broadcast('responseError', error);
console.log(arguments);
return $q.reject(response);
// Needs refresh
} else if (response.needRefresh) {
$rootScope.$broadcast('responseError', "The site has been updated and the page needs to refresh. The last action has not been recorded, please refresh and try again.");
// 400 range?
} else if (response < 500) {
$rootScope.$broadcast('responseText', response.data.err || response.data);
// Error
} else {
var error = '<strong>Please reload</strong>, ' +
'"'+window.env.t('error')+' '+(response.data.err || response.data || 'something went wrong')+'" ' +
window.env.t('seeConsole');
$rootScope.$broadcast('responseError', error);
console.error(response);
}
//return $q.reject(response); // this completely halts the chain, meaning we can't queue offline actions
return response;
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
}]);
}])
+20 -25
View File
@@ -5,15 +5,15 @@
*/
angular.module('authCtrl', [])
.controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','API_URL',
function($scope, $rootScope, User, $http, $location, $window, API_URL) {
.controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','API_URL', '$modal',
function($scope, $rootScope, User, $http, $location, $window, API_URL, $modal) {
var runAuth;
var showedFacebookMessage;
$scope.useUUID = false;
$scope.toggleUUID = function() {
if (showedFacebookMessage === false) {
alert("Until we add Facebook, use your UUID and API Token to log in (found at https://habitrpg.com > Options > Settings).");
alert(window.env.t('untilNoFace'));
showedFacebookMessage = true;
}
$scope.useUUID = !$scope.useUUID;
@@ -27,10 +27,19 @@ angular.module('authCtrl', [])
runAuth = function(id, token) {
User.authenticate(id, token, function(err) {
$window.location.href = '/';
//$rootScope.modals.login = false;
});
};
function errorAlert(data, status, headers, config) {
if (status === 0) {
$window.alert(window.env.t('noReachServer'));
} else if (!!data && !!data.err) {
$window.alert(data.err);
} else {
$window.alert(window.env.t('errorUpCase') + ' ' + status);
}
};
$scope.register = function() {
/*TODO highlight invalid inputs
we have this as a workaround for https://github.com/HabitRPG/habitrpg-mobile/issues/64
@@ -40,27 +49,9 @@ angular.module('authCtrl', [])
}
$http.post(API_URL + "/api/v2/register", $scope.registerVals).success(function(data, status, headers, config) {
runAuth(data.id, data.apiToken);
}).error(function(data, status, headers, config) {
if (status === 0) {
$window.alert("Server not currently reachable, try again later");
} else if (!!data && !!data.err) {
$window.alert(data.err);
} else {
$window.alert("ERROR: " + status);
}
});
}).error(errorAlert);
};
function errorAlert(data, status, headers, config) {
if (status === 0) {
$window.alert("Server not currently reachable, try again later");
} else if (!!data && !!data.err) {
$window.alert(data.err);
} else {
$window.alert("ERROR: " + status);
}
}
$scope.auth = function() {
var data = {
username: $scope.loginUsername || $('#login-tab input[name="username"]').val(),
@@ -77,17 +68,21 @@ angular.module('authCtrl', [])
};
$scope.playButtonClick = function(){
window.ga && ga('send', 'event', 'button', 'click', 'Play');
if (User.authenticated()) {
window.location.href = '/#/tasks';
} else {
$('#login-modal').modal('show');
$modal.open({
templateUrl: 'modals/login.html'
// Using controller: 'AuthCtrl' it causes problems
});
}
}
$scope.passwordReset = function(email){
$http.post(API_URL + '/api/v2/user/reset-password', {email:email})
.success(function(){
alert('New password sent.');
alert(window.env.t('newPassSent'));
})
.error(function(data){
alert(data.err);
+8 -8
View File
@@ -44,11 +44,11 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User',
* Save
*/
$scope.save = function(challenge) {
if (!challenge.group) return alert('Please select group');
if (!challenge.group) return alert(window.env.t('selectGroup'));
var isNew = !challenge._id;
challenge.$save(function(_challenge){
if (isNew) {
Notification.text('Challenge Created');
Notification.text(window.env.t('challengeCreated'));
$state.go('options.social.challenges.detail', {cid: _challenge._id});
$scope.discard();
$scope.challenges = Challenges.Challenge.query();
@@ -85,14 +85,14 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User',
$scope.closingChal = undefined;
}
$scope["delete"] = function(challenge) {
if (!confirm("Delete challenge, are you sure?")) return;
if (!confirm(window.env.t('sureDelCha'))) return;
challenge.$delete(function(){
$scope.popoverEl.popover('destroy');
backToChallenges();
});
};
$scope.selectWinner = function(challenge) {
if (!confirm("Are you sure?")) return;
if (!confirm(window.env.t('youSure'))) return;
challenge.$close({uid:challenge.winner}, function(){
$scope.popoverEl.popover('destroy');
backToChallenges();
@@ -106,7 +106,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User',
html: true,
placement: 'right',
trigger: 'manual',
title: 'Close challenge and...',
title: window.env.t('closeCha'),
content: html
}).popover('show');
@@ -140,7 +140,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User',
};
$scope.removeTask = function(list, $index) {
if (!confirm("Are you sure you want to delete this task?")) return;
if (!confirm(window.env.t('sureDelete'))) return;
//TODO persist
// User.log({op: "delTask", data: task});
list.splice($index, 1);
@@ -185,13 +185,13 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User',
$scope.selectedChal = chal;
$scope.popoverEl = $($event.target);
var html = $compile(
'<a ng-controller="ChallengesCtrl" ng-click="leave(\'remove-all\')">Remove Tasks</a><br/>\n<a ng-click="leave(\'keep-all\')">Keep Tasks</a><br/>\n<a ng-click="leave(\'cancel\')">Cancel</a><br/>'
'<a ng-controller="ChallengesCtrl" ng-click="leave(\'remove-all\')">' + window.env.t('removeTasks') + '</a><br/>\n<a ng-click="leave(\'keep-all\')">' + window.env.t('keepTasks') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
)($scope);
$scope.popoverEl.popover('destroy').popover({
html: true,
placement: 'top',
trigger: 'manual',
title: 'Leave challenge and...',
title: window.env.t('leaveCha'),
content: html
}).popover('show');
}
+33 -32
View File
@@ -1,6 +1,7 @@
"use strict";
habitrpg.controller("FooterCtrl", ['$scope', '$rootScope', 'User', '$http', 'Notification', 'API_URL',
(typeof habitrpg !== 'undefined' ? habitrpg : habitrpgStatic)
.controller("FooterCtrl", ['$scope', '$rootScope', 'User', '$http', 'Notification', 'API_URL',
function($scope, $rootScope, User, $http, Notification, API_URL) {
/**
@@ -13,17 +14,14 @@ habitrpg.controller("FooterCtrl", ['$scope', '$rootScope', 'User', '$http', 'Not
// Stripe
$.getScript('//checkout.stripe.com/v2/checkout.js');
// Amazon Affiliate
// if ($rootScope.authenticated() && !User.user.purchased.ads) {
// $.getScript('//wms.assoc-amazon.com/20070822/US/js/link-enhancer-common.js?tag=ha0d2-20').fail(function() {
// $('body').append('<img src="//wms.assoc-amazon.com/20070822/US/img/noscript.gif?tag=ha0d2-20" alt="" />');
// });
// }
// Google Analytics, only in production
if (window.env.NODE_ENV === 'production') {
window._gaq = [["_setAccount", "UA-33510635-1"], ["_setDomainName", "habitrpg.com"], ["_trackPageview"]];
$.getScript(("https:" === document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js");
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', window.env.GA_ID, 'habitrpg.com');
ga('send', 'pageview');
}
// Scripts only for desktop
@@ -44,27 +42,30 @@ habitrpg.controller("FooterCtrl", ['$scope', '$rootScope', 'User', '$http', 'Not
/**
* Debug functions. Note that the server route for gems is only available if process.env.DEBUG=true
*/
$scope.addMissedDay = function(){
if (!confirm("Are you sure you want to reset the day?")) return;
var dayBefore = moment(User.user.lastCron).subtract('days', 1).toDate();
User.set({'lastCron': dayBefore});
Notification.text('-1 day, remember to refresh');
}
$scope.addTenGems = function(){
$http.post(API_URL + '/api/v2/user/addTenGems').success(function(){
User.log({});
})
}
$scope.addLevelsAndGold = function(){
User.set({
'stats.exp': User.user.stats.exp + 10000,
'stats.gp': User.user.stats.gp + 10000,
'stats.mp': User.user.stats.mp + 10000
});
}
$scope.addOneLevel = function(){
User.set({
'stats.exp': User.user.stats.exp + (Math.round(((Math.pow(User.user.stats.lvl, 2) * 0.25) + (10 * User.user.stats.lvl) + 139.75) / 10) * 10)
});
if (window.env.NODE_ENV === 'development') {
$scope.addMissedDay = function(){
if (!confirm("Are you sure you want to reset the day?")) return;
var dayBefore = moment(User.user.lastCron).subtract('days', 1).toDate();
User.set({'lastCron': dayBefore});
Notification.text('-1 day, remember to refresh');
}
$scope.addTenGems = function(){
$http.post(API_URL + '/api/v2/user/addTenGems').success(function(){
User.log({});
})
}
$scope.addLevelsAndGold = function(){
User.set({
'stats.exp': User.user.stats.exp + 10000,
'stats.gp': User.user.stats.gp + 10000,
'stats.mp': User.user.stats.mp + 10000
});
}
$scope.addOneLevel = function(){
User.set({
'stats.exp': User.user.stats.exp + (Math.round(((Math.pow(User.user.stats.lvl, 2) * 0.25) + (10 * User.user.stats.lvl) + 139.75) / 10) * 10)
});
}
}
}])
+24 -21
View File
@@ -29,12 +29,12 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
// We need the member information up top here, but then we pass it down to the modal controller
// down below. Better way of handling this?
Members.selectMember(uid);
$rootScope.modals.member = true;
$rootScope.openModal('member', {controller:'MemberModalCtrl'});
}
}
$scope.removeMember = function(group, member, isMember){
var yes = confirm("Do you really want to remove this member from the party?")
var yes = confirm(window.env.t('sureKick'))
if(yes){
Groups.Group.removeMember({gid: group._id, uuid: member._id }, undefined, function(){
if(isMember){
@@ -99,8 +99,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
$scope.$watch('group.chat',$scope.chatChanged);
$scope.caretChanged = function(newCaretPos) {
var relativeelement = $('.-options');
var textarea = $('.chat-textarea');
var relativeelement = $('.chat-form div:first');
var textarea = $('.chat-form textarea');
var userlist = $('.list-at-user');
var offset = {
x: textarea.offset().left - relativeelement.offset().left,
@@ -180,7 +180,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
$scope.likeChatMessage = function(group,message) {
if (message.uuid == User.user._id)
return Notification.text("Can't like your own message. Don't be that person.");
return Notification.text(window.env.t('foreverAlone'));
if (!message.likes) message.likes = {};
if (message.likes[User.user._id]) {
delete message.likes[User.user._id];
@@ -198,10 +198,10 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
// List of Ordering options for the party members list
$scope.partyOrderChoices = {
'level': 'Sort by Level',
'random': 'Sort randomly',
'pets': 'Sort by number of pets',
'party_date_joined': 'Sort by Party date joined',
'level': window.env.t('sortLevel'),
'random': window.env.t('sortRandom'),
'pets': window.env.t('sortPets'),
'party_date_joined': window.env.t('sortJoined'),
};
}])
@@ -219,9 +219,9 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
}
$scope.newGroup = newGroup()
$scope.create = function(group){
if (User.user.balance < 1) return $rootScope.modals.buyGems = true;
if (User.user.balance < 1) return $rootScope.openModal('buyGems', {track:"Gems > Create Group"});
if (confirm("Create Guild for 4 Gems?")) {
if (confirm(window.env.t('confirmGuild'))) {
group.$save(function(saved){
User.user.balance--;
$scope.groups.guilds.push(saved);
@@ -284,14 +284,14 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
}), '_id');
if (_.intersection(challenges, User.user.challenges).length > 0) {
html = $compile(
'<a ng-controller="GroupsCtrl" ng-click="leave(\'remove-all\')">Remove Tasks</a><br/>\n<a ng-click="leave(\'keep-all\')">Keep Tasks</a><br/>\n<a ng-click="leave(\'cancel\')">Cancel</a><br/>'
'<a ng-controller="GroupsCtrl" ng-click="leave(\'remove-all\')">' + window.env.t('removeTasks') + '</a><br/>\n<a ng-click="leave(\'keep-all\')">' + window.env.t('keepTasks') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
)($scope);
title = "Leave group challenges and...";
title = window.env.t('leaveGroupCha');
} else {
html = $compile(
'<a ng-controller="GroupsCtrl" ng-click="leave(\'keep-all\')">Confirm</a><br/>\n<a ng-click="leave(\'cancel\')">Cancel</a><br/>'
'<a ng-controller="GroupsCtrl" ng-click="leave(\'keep-all\')">' + window.env.t('confirm') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
)($scope);
title = "Leave group?"
title = window.env.t('leaveGroup')
}
$scope.popoverEl.popover('destroy').popover({
html: true,
@@ -319,6 +319,9 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
$scope.text = 'Party';
$scope.group = $rootScope.party = Groups.party();
$scope.newGroup = new Groups.Group({type:'party'});
Groups.seenMessage($scope.group._id);
$scope.create = function(group){
group.$save(function(newGroup){
$scope.group = newGroup;
@@ -358,14 +361,14 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
}), '_id');
if (_.intersection(challenges, User.user.challenges).length > 0) {
html = $compile(
'<a ng-controller="GroupsCtrl" ng-click="leave(\'remove-all\')">Remove Tasks</a><br/>\n<a ng-click="leave(\'keep-all\')">Keep Tasks</a><br/>\n<a ng-click="leave(\'cancel\')">Cancel</a><br/>'
'<a ng-controller="GroupsCtrl" ng-click="leave(\'remove-all\')">' + window.env.t('removeTasks') + '</a><br/>\n<a ng-click="leave(\'keep-all\')">' + window.env.t('keepTasks') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
)($scope);
title = "Leave party challenges and...";
title = window.env.t('leavePartyCha');
} else {
html = $compile(
'<a ng-controller="GroupsCtrl" ng-click="leave(\'keep-all\')">Confirm</a><br/>\n<a ng-click="leave(\'cancel\')">Cancel</a><br/>'
'<a ng-controller="GroupsCtrl" ng-click="leave(\'keep-all\')">' + window.env.t('confirm') + '</a><br/>\n<a ng-click="leave(\'cancel\')">' + window.env.t('cancel') + '</a><br/>'
)($scope);
title = "Leave party?";
title = window.env.t('leaveParty');
}
$scope.popoverEl.popover('destroy').popover({
html: true,
@@ -383,8 +386,8 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
}
$scope.questAbort = function(){
if (!confirm("Are you sure you want to abort this mission? It will abort it for everyone in your party, and you'll lose your quest scroll.")) return;
if (!confirm("Are you double sure? Make sure they won't hate you forever!")) return;
if (!confirm(window.env.t('sureAbort'))) return;
if (!confirm(window.env.t('doubleSureAbort'))) return;
$rootScope.party.$questAbort();
}
}
+16 -13
View File
@@ -7,7 +7,8 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User',
$scope.selectedEgg = null; // {index: 1, name: "Tiger", value: 5}
$scope.selectedPotion = null; // {index: 5, name: "Red", value: 3}
$scope.totalPets = _.size(Content.eggs) * _.size(Content.hatchingPotions);
$scope.totalPets = _.size(Content.dropEggs) * _.size(Content.hatchingPotions);
$scope.totalMounts = _.size(Content.eggs) * _.size(Content.hatchingPotions);
// count egg, food, hatchingPotion stack totals
var countStacks = function(items) { return _.reduce(items,function(m,v){return m+v;},0);}
@@ -79,7 +80,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User',
}
$scope.hatch = function(egg, potion){
if (!$window.confirm('Hatch a ' + potion.key + ' ' + egg.key + '?')) return;
if (!$window.confirm(window.env.t('hatchAPot', {potion: potion.key, egg: egg.key}))) return;
user.ops.hatch({params:{egg:egg.key, hatchingPotion:potion.key}});
$scope.selectedEgg = null;
$scope.selectedPotion = null;
@@ -87,9 +88,11 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User',
$scope.purchase = function(type, item){
var gems = User.user.balance * 4;
if(gems < item.value) return $rootScope.modals.buyGems = true;
var string = (type == 'hatchingPotion') ? 'hatching potion' : type; // give hatchingPotion a space
var message = "Buy this " + string + " with " + item.value + " of your " + gems + " Gems?"
if(gems < item.value) return $rootScope.openModal('buyGems');
var string = (type == 'hatchingPotions') ? 'hatching potion' : (type == 'eggs') ? 'egg' : (type == 'quests') ? 'quest' : (item.key == 'Saddle') ? 'saddle' : (type == 'special') ? item.key : type; // this is ugly but temporary, once the purchase modal is done this will be removed
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}});
}
@@ -102,8 +105,8 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User',
if ($scope.selectedFood) {
var food = $scope.selectedFood
if (food.key == 'Saddle') {
if (!$window.confirm('Saddle ' + pet + '?')) return;
} else if (!$window.confirm('Feed ' + petDisplayName + ' '+ food.article + food.text + '?')) {
if (!$window.confirm(window.env.t('useSaddle', {pet: pet}))) return;
} else if (!$window.confirm(window.env.t('feedPet', {name: petDisplayName, article: food.article, text: food.text}))) {
return;
}
User.user.ops.feed({params:{pet: pet, food: food.key}});
@@ -123,16 +126,14 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User',
var item = Content.quests[quest];
var completedPrevious = !item.previous || (User.user.achievements.quests && User.user.achievements.quests[item.previous]);
if (!completedPrevious)
return alert("You must first complete " + $rootScope.Content.quests[item.previous].text + '.');
return alert(window.env.t('mustComplete', {quest: $rootScope.Content.quests[item.previous].text}));
if (item.lvl && item.lvl > user.stats.lvl)
return alert("You must be level " + item.lvl + '.');
return alert(window.env.t('mustLevel', {level: item.lvl}));
$rootScope.selectedQuest = item;
$rootScope.modals.showQuest = true;
$rootScope.openModal('showQuest', {controller:'InventoryCtrl'});
}
$scope.closeQuest = function(){
$rootScope.selectedQuest = undefined;
$rootScope.modals.showQuest = false;
$rootScope.modals.buyQuest = false;
}
$scope.questInit = function(){
$rootScope.party.$questAccept({key:$scope.selectedQuest.key}, function(){
@@ -142,11 +143,13 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User',
}
$scope.buyQuest = function(quest) {
var item = Content.quests[quest];
if (item.lvl && item.lvl > user.stats.lvl)
return alert(window.env.t('mustLvlQuest', {level: item.lvl}));
var completedPrevious = !item.previous || (User.user.achievements.quests && User.user.achievements.quests[item.previous]);
if (!completedPrevious)
return $scope.purchase("quests", item);
$rootScope.selectedQuest = item;
$rootScope.modals.buyQuest = true;
$rootScope.openModal('buyQuest', {controller:'InventoryCtrl'});
}
}
]);
+37 -7
View File
@@ -1,9 +1,13 @@
'use strict';
habitrpg.controller('NotificationCtrl',
['$scope', '$rootScope', 'Shared', 'User', 'Guide', 'Notification', function ($scope, $rootScope, Shared, User, Guide, Notification) {
['$scope', '$rootScope', 'Shared', 'User', 'Guide', 'Notification',
function ($scope, $rootScope, Shared, User, Guide, Notification) {
$rootScope.$watch('user.stats.hp', function(after, before) {
if (after <= 0){
$rootScope.openModal('death', {keyboard:false, backdrop:'static'});
}
if (after == before) return;
if (User.user.stats.lvl == 0) return;
Notification.hp(after - before, 'hp');
@@ -26,7 +30,7 @@ habitrpg.controller('NotificationCtrl',
if ((money > 0) && !!bonus) {
if (bonus < 0.01) bonus = 0.01;
Notification.text("+ " + Notification.coins(bonus) + " Streak Bonus!");
Notification.text("+ " + Notification.coins(bonus) + ' ' + window.env.t('streakCoins'));
delete User.user._tmp.streakBonus;
}
});
@@ -61,31 +65,35 @@ habitrpg.controller('NotificationCtrl',
$rootScope.$watch('user.achievements.streak', function(after, before){
if(before == undefined || after == before || after < before) return;
$rootScope.modals.achievements.streak = true;
$rootScope.openModal('achievements/streak');
});
$rootScope.$watch('user.achievements.ultimateGear', function(after, before){
if (after === before || after !== true) return;
$rootScope.modals.achievements.ultimateGear = true;
$rootScope.openModal('achievements/ultimateGear');
});
$rootScope.$watch('user.items.pets', function(after, before){
if(_.size(after) === _.size(before) ||
Shared.countPets(null, after) < 90) return;
User.user.achievements.beastMaster = true;
$rootScope.modals.achievements.beastMaster = true;
$rootScope.openModal('achievements/beastMaster');
}, true);
$rootScope.$watch('user.achievements.rebirths', function(after, before){
if(after === before) return;
$rootScope.modals.achievements.rebirth = true;
$rootScope.openModal('achievements/rebirth');
});
$rootScope.$watch('user.flags.contributor', function(after, before){
if (after === before || after !== true) return;
$rootScope.openModal('achievements/contributor');
});
/*_.each(['weapon', 'head', 'chest', 'shield'], function(watched){
$rootScope.$watch('user.items.' + watched, function(before, after){
if (after == before) return;
if (+after < +before) {
Notification.death();
//don't want to day "lost a head"
if (watched === 'head') watched = 'helm';
Notification.text('Lost GP, 1 LVL, ' + watched);
@@ -93,6 +101,13 @@ habitrpg.controller('NotificationCtrl',
})
});*/
// Classes modal
$rootScope.$watch('!user.flags.classSelected && user.stats.lvl >= 10', function(after, before){
if(after){
$rootScope.openModal('chooseClass', {controller:'UserCtrl', keyboard:false, backdrop:'static'});
}
});
$rootScope.$watch('user.stats.lvl', function(after, before) {
if (after == before) return;
if (after > before) {
@@ -100,8 +115,23 @@ habitrpg.controller('NotificationCtrl',
}
});
// Completed quest modal
$rootScope.$watch('user.party.quest.completed', function(after, before){
if (after == before || after != true) return;
$rootScope.openModal('questCompleted', {controller:'InventoryCtrl'});
});
// Quest invitation modal
$rootScope.$watch('party.quest.key && !party.quest.active && party.quest.members[user._id] == undefined', function(after, before){
if (after == before || after != true) return;
$rootScope.openModal('questInvitation');
});
$rootScope.$on('responseError', function(ev, error){
Notification.error(error);
});
$rootScope.$on('responseText', function(ev, error){
Notification.text(error);
});
}
]);
+59 -19
View File
@@ -3,8 +3,8 @@
/* Make user and settings available for everyone through root scope.
*/
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content',
function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content) {
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout',
function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout) {
var user = User.user;
var initSticky = _.once(function(){
@@ -13,8 +13,11 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
})
$rootScope.$on('userUpdated',initSticky);
$rootScope.modals = {};
$rootScope.modals.achievements = {};
$rootScope.$on('$stateChangeSuccess',
function(event, toState, toParams, fromState, fromParams){
if (!!fromState.name) window.ga && ga('send', 'pageview', {page: '/#/'+toState.name});
});
$rootScope.User = User;
$rootScope.user = user;
$rootScope.moment = window.moment;
@@ -23,6 +26,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
$rootScope.Shared = Shared;
$rootScope.Content = Content;
$rootScope.env = window.env;
$rootScope.Math = Math;
// Angular UI Router
$rootScope.$state = $state;
@@ -66,29 +70,60 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
$rootScope.set = User.set;
$rootScope.authenticated = User.authenticated;
// Open a modal from a template expression (like ng-click,...)
// Otherwise use the proper $modal.open
$rootScope.openModal = function(template, options){//controller, scope, keyboard, backdrop){
if (!options) options = {};
if (options.track) window.ga && ga('send', 'event', 'button', 'click', options.track);
return $modal.open({
templateUrl: 'modals/' + template + '.html',
controller: options.controller, // optional
scope: options.scope, // optional
keyboard: (options.keyboard === undefined ? true : options.keyboard), // optional
backdrop: (options.backdrop === undefined ? true : options.backdrop) // optional
});
}
$rootScope.dismissAlert = function() {
$rootScope.modals.newStuff = false;
$rootScope.set({'flags.newStuff':false});
}
$rootScope.notPorted = function(){
alert("This feature is not yet ported from the original site.");
alert(window.env.t('notPorted'));
}
$rootScope.dismissErrorOrWarning = function(type, $index){
$rootScope.flash[type].splice($index, 1);
}
$rootScope.initPayPalButton = function($event){
//debugger
var data = {
name:env.t('paypalText'),
//env:window.env.NODE_ENV == 'production' ? '' : 'sandbox',
quantity:1,
amount:5,
currency:'USD',
tax:0,
callback:window.env.BASE_URL + '/api/v2/user/buy-gems/paypal-ipn',
custom:'?uid='+User.user._id + '&apiToken=' + User.user.apiToken,
'return':window.env.BASE_URL,
rm:1,
no_shipping:1
};
PAYPAL.apps.ButtonFactory.create(window.env.PAYPAL_MERCHANT, data, 'buynow', document.getElementById('custom-paypal-button'));
}
$rootScope.showStripe = function(subscription) {
StripeCheckout.open({
key: window.env.STRIPE_PUB_KEY,
address: false,
amount: 500,
name: subscription ? "Subscribe" : "Checkout",
name: subscription ? window.env.t('subscribe') : window.env.t('checkout'),
description: subscription ?
"Buy gems with Gold, No Ads, Support the Devs" :
"20 Gems, No Ads, Support the Devs",
panelLabel: subscription ? "Subscribe" : "Checkout",
window.env.t('buySubsText') :
window.env.t('buyCheckText'),
panelLabel: subscription ? window.env.t('subscribe') : window.env.t('checkout'),
token: function(data) {
var url = '/api/v2/user/buy-gems';
if (subscription) url += '?plan=basic_earned';
@@ -105,7 +140,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
}
$scope.cancelSubscription = function(){
if (!confirm("Are you sure you want to cancel your subscription?")) return;
if (!confirm(window.env.t('sureCancelSub'))) return;
//TODO use Stripe API to keep subscription till end of their month
$http.post('/api/v2/user/cancel-subscription').success(function(){
window.location.reload(true);
@@ -117,7 +152,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
if (backer && backer.npc) return backer.npc;
var l = contrib && contrib.level;
if (l && l > 0) {
var level = (l < 3) ? 'Friend' : (l < 5) ? 'Elite' : (l < 7) ? 'Champion' : (l < 8) ? 'Legendary' : 'Heroic';
var level = (l < 3) ? window.env.t('friend') : (l < 5) ? window.env.t('elite') : (l < 7) ? window.env.t('champion') : (l < 8) ? window.env.t('legendary') : window.env.t('heroic');
return level + ' ' + contrib.text;
}
}
@@ -145,7 +180,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
});
data = google.visualization.arrayToDataTable(matrix);
options = {
title: 'History',
title: window.env.t('history'),
backgroundColor: {
fill: 'transparent'
},
@@ -163,7 +198,12 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
------------------------
*/
$scope.castStart = function(spell) {
if (User.user.stats.mp < spell.mana) return Notification.text("Not enough mana.");
if (User.user.stats.mp < spell.mana) return Notification.text(window.env.t('notEnoughMana'));
// Temporary for valentine's day, remove after event
if (spell.key == 'valentine' && User.user.stats.gp < spell.value)
return Notification.text('Not enough gold.');
$rootScope.applyingAction = true;
$scope.spell = spell;
if (spell.target == 'self') {
@@ -178,7 +218,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
$scope.castEnd = function(target, type, $event){
if (!$rootScope.applyingAction) return;
$event && ($event.stopPropagation(),$event.preventDefault());
if ($scope.spell.target != type) return Notification.text("Invalid target");
if ($scope.spell.target != type) return Notification.text(window.env.t('invalidTarget'));
$scope.spell.cast(User.user, target);
User.save();
@@ -189,11 +229,11 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
$http.post('/api/v2/user/class/cast/'+spell.key+'?targetType='+type+'&targetId='+targetId)
.success(function(){
var msg = "You cast " + spell.text;
var msg = window.env.t('youCast', {spell: spell.text});
switch (type) {
case 'task': msg += ' on ' + target.text + '.';break;
case 'user': msg += ' on ' + target.profile.name + '.';break;
case 'party': msg += ' for the Party.';break;
case 'task': msg = window.env.t('youCastTarget', {spell: spell.text, target: target.text});break;
case 'user': msg = window.env.t('youCastTarget', {spell: spell.text, target: target.profile.name});break;
case 'party': msg = window.env.t('youCastParty', {spell: spell.text});break;
}
Notification.text(msg);
});
+8 -13
View File
@@ -38,7 +38,7 @@ habitrpg.controller('SettingsCtrl',
var dayStart = +User.user.preferences.dayStart;
if (_.isNaN(dayStart) || dayStart < 0 || dayStart > 24) {
dayStart = 0;
return alert('Please enter a number between 0 and 24');
return alert(window.env.t('enterNumber'));
}
User.set({'preferences.dayStart': dayStart});
}
@@ -55,23 +55,21 @@ habitrpg.controller('SettingsCtrl',
$scope.reroll = function(){
User.user.ops.reroll({});
$rootScope.modals.reroll = false;
$rootScope.$state.go('tasks');
}
$scope.rebirth = function(){
User.user.ops.rebirth({});
$rootScope.modals.rebirth = false;
$rootScope.$state.go('tasks');
}
$scope.changePassword = function(changePass){
if (!changePass.oldPassword || !changePass.newPassword || !changePass.confirmNewPassword) {
return alert("Please fill out all fields");
return alert(window.env.t('fillAll'));
}
$http.post(API_URL + '/api/v2/user/change-password', changePass)
.success(function(){
alert("Password successfully changed");
alert(window.env.t('passSuccess'));
$scope.changePass = {};
})
.error(function(data){
@@ -80,12 +78,11 @@ habitrpg.controller('SettingsCtrl',
}
$scope.restoreValues = {};
$rootScope.$watch('modals.restore', function(value){
if(value === true){
$scope.restoreValues.stats = angular.copy(User.user.stats);
$scope.restoreValues.achievements = {streak: User.user.achievements.streak || 0};
}
})
$rootScope.openRestoreModal = function(){
$scope.restoreValues.stats = angular.copy(User.user.stats);
$scope.restoreValues.achievements = {streak: User.user.achievements.streak || 0};
$rootScope.openModal('restore', {scope:$scope});
};
$scope.restore = function(){
var stats = $scope.restoreValues.stats,
@@ -98,12 +95,10 @@ habitrpg.controller('SettingsCtrl',
"stats.mp": stats.mp,
"achievements.streak": achievements.streak
});
$rootScope.modals.restore = false;
}
$scope.reset = function(){
User.user.ops.reset({});
$rootScope.modals.reset = false;
$rootScope.$state.go('tasks');
}
+1 -1
View File
@@ -38,7 +38,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$location', 'User','Notification',
};
$scope.removeTask = function(list, $index) {
if (!confirm("Are you sure you want to delete this task?")) return;
if (!confirm(window.env.t('sureDelete'))) return;
User.user.ops.deleteTask({params:{id:list[$index].id}})
};
+5 -5
View File
@@ -18,7 +18,7 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$
$scope.changeClass = function(klass){
if (!klass) {
if (!confirm("Are you sure? This will reset your character's class and allocated points (you'll get them all back to re-allocate), and costs 3 gems"))
if (!confirm(window.env.t('sureReset')))
return;
return User.user.ops.changeClass({});
}
@@ -52,11 +52,11 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$
var cost = fullSet ? 1.25 : 0.5; // 5G per set, 2G per individual
if (fullSet) {
if (confirm("Purchase for 5 Gems?") !== true) return;
if (User.user.balance < cost) return $rootScope.modals.buyGems = true;
if (confirm(window.env.t('purchaseFor5')) !== true) return;
if (User.user.balance < cost) return $rootScope.openModal('buyGems');
} else if (!User.user.fns.dotGet('purchased.' + path)) {
if (confirm("Purchase for 2 Gems?") !== true) return;
if (User.user.balance < cost) return $rootScope.modals.buyGems = true;
if (confirm(window.env.t('purchaseFor2')) !== true) return;
if (User.user.balance < cost) return $rootScope.openModal('buyGems');
}
User.user.ops.unlock({query:{path:path}})
}
+9 -9
View File
@@ -26,7 +26,7 @@ habitrpg.directive('habitrpgAdsense', function() {
template: '<div ng-transclude></div>',
link: function ($scope, element, attrs) {}
}
})
});
habitrpg.directive('whenScrolled', function() {
return function(scope, elm, attr) {
@@ -85,21 +85,21 @@ habitrpg
scope.main = attrs.main;
$rootScope.lists = [
{
header: env.t('habits'),
header: window.env.t('habits'),
type: 'habit',
placeHolder: env.t('newHabit')
placeHolder: window.env.t('newHabit')
}, {
header: env.t('dailies'),
header: window.env.t('dailies'),
type: 'daily',
placeHolder: env.t('newDaily')
placeHolder: window.env.t('newDaily')
}, {
header: env.t('todos'),
header: window.env.t('todos'),
type: 'todo',
placeHolder: env.t('newTodo')
placeHolder: window.env.t('newTodo')
}, {
header: env.t('rewards'),
header: window.env.t('rewards'),
type: 'reward',
placeHolder: env.t('newReward')
placeHolder: window.env.t('newReward')
}
];
+20
View File
@@ -0,0 +1,20 @@
"use strict";
window.env = window.env || {}; //FIX tests
// If Moment.js is loaded,
if(window.moment && window.env.language && window.env.language.momentLang && window.env.language.momentLangCode){
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.text = window.env.language.momentLang;
head.appendChild(script);
window.moment.lang(window.env.language.momentLangCode);
}
window.env.t = function(stringName, vars){
var string = window.env.translations[stringName];
if(!string) return window._.template(window.env.translations.stringNotFound, {string: stringName});
return vars === undefined ? string : window._.template(string, vars);
}
+1 -1
View File
@@ -32,7 +32,7 @@ factory('Facebook',
$http.post(API_URL + '/api/v2/user/auth/facebook', data).success(function(data, status, headers, config) {
User.authenticate(data.id, data.token, function(err) {
if (!err) {
alert('Login successful!');
alert(window.env.t('loginSuccess'));
$location.path("/habit");
}
});
+8 -2
View File
@@ -5,8 +5,8 @@
*/
angular.module('groupServices', ['ngResource']).
factory('Groups', ['API_URL', '$resource', '$q',
function(API_URL, $resource, $q) {
factory('Groups', ['API_URL', '$resource', '$q', '$http', 'User',
function(API_URL, $resource, $q, $http, User) {
var Group = $resource(API_URL + '/api/v2/groups/:gid',
{gid:'@_id', messageId: '@_messageId'},
{
@@ -44,6 +44,12 @@ angular.module('groupServices', ['ngResource']).
return tavern;
},
// On enter, set chat message to "seen"
seenMessage: function(gid){
$http.post('/api/v2/groups/'+gid+'/chat/seen');
if (User.user.newMessages) User.user.newMessages[gid] = false;
},
Group: Group
}
}
+42 -50
View File
@@ -18,58 +18,58 @@ angular.module('guideServices', []).
var tourSteps = [
{
element: ".main-herobox",
title: "Welcome to HabitRPG",
content: "Welcome to HabitRPG, a habit-tracker which treats your goals like a Role Playing Game. I'm <a href='http://www.kickstarter.com/profile/1823740484' target='_blank'>Justin</a>, your guide!",
title: window.env.t('welcomeHabit'),
content: window.env.t('welcomeHabitT1') + " <a href='http://www.kickstarter.com/profile/1823740484' target='_blank'>Justin</a>, " + window.env.t('welcomeHabitT2'),
}, {
element: ".main-herobox",
title: "Your Avatar",
content: "This is your avatar. It represents you in the world of Habitica. As you accomplish goals, your avatar will gain levels, earn gold, and equip itself for further challenges ahead.",
title: window.env.t('yourAvatar'),
content: window.env.t('yourAvatarText'),
}, {
element: ".main-herobox",
title: "Avatar Customization",
content: "You can customize your avatar by clicking anywhere in this box. Change your body type, hair color, skin color, and more from this menu. You can also find a number of HabitRPG's exciting social features by clicking through tabs on the customization page.",
title: window.env.t('avatarCustom'),
content: window.env.t('avatarCustomText'),
}, {
element: "#bars",
title: "Hit Points",
content: "The red bar tracks your avatar's health points. Whenever you fail to meet a goal, you take damage and lose health. If your health bar reaches zero, you die. Dying results in the loss of one level, all your gold, and a piece of equipment.",
title: window.env.t('hitPoints'),
content: window.env.t('hitPointsText'),
}, {
element: "#bars",
title: "Experience Points",
content: "The yellow bar tracks your avatar's experience points. Whenever you succeed in achieving a goal, you gain both gold and experience. When your experience bar maxes out, you gain a level. Gaining levels is how you unlock new and exciting features on HabitRPG.",
title: window.env.t('expPoints'),
content: window.env.t('expPointsText'),
}, {
element: "ul.habits",
title: "Types of Goals",
content: "HabitRPG allows you to track your goals in three different ways. These goals are categorized in columns as Habits, Dailies, or To-Dos.",
title: window.env.t('typeGoals'),
content: window.env.t('typeGoalsText'),
placement: "top"
}, {
element: "ul.habits",
title: "Habits",
content: "Habits are goals that you constantly track. They can be given plus or minus values, allowing you to gain experience and gold for good habits or lose health for bad ones.",
title: window.env.t('habits'),
content: window.env.t('tourHabits'),
placement: "top"
}, {
element: "ul.dailys",
title: "Dailies",
content: "Dailies are goals that you want to complete once a day. Checking off a daily reaps experience and gold. Failing to check off your daily before the day resets results in a loss of health. You can change your day start settings from the options menu.",
title: window.env.t('dailies'),
content: window.env.t('tourDailies'),
placement: "top"
}, {
element: "ul.todos",
title: "To-Dos",
content: "To-Dos are one-off goals that you can get to eventually. While it is possible to set a deadline on a to-do, they are not required. To-Dos make for a quick and easy way to gain experience.",
title: window.env.t('todos'),
content: window.env.t('tourTodos'),
placement: "top"
}, {
element: "ul.main-list.rewards",
title: "Rewards",
content: "All that gold you earned will allow you to reward yourself with either custom or in-game prizes. Buy them liberally rewarding yourself is integral in forming good habits.",
title: window.env.t('rewards'),
content: window.env.t('tourRewards'),
placement: "top"
}, {
element: "ul.habits li:first-child",
title: "Hover over comments",
content: "You can add comments to your tasks by clicking the edit icon. Hover over each task's comment for more details about how HabitRPG works. When you're ready to get started, you can delete the existing tasks and add your own.",
title: window.env.t('hoverOver'),
content: window.env.t('hoverOverText'),
placement: "right"
}, {
element: "ul.habits li:first-child",
title: "Unlock New Features",
content: "That's all you need to know for now, but I'll be back as you level up to let you know about new features you've unlocked. Each new feature will give you more incentives to accomplish your goals. Find out more at the <a href='http://habitrpg.wikia.com' target='_blank'>HabitRPG Wiki</a> or let yourself be surprised. Good luck!",
title: window.env.t('unlockFeatures'),
content: window.env.t('unlockFeaturesT1') + " <a href='http://habitrpg.wikia.com' target='_blank'>" + window.env.t('habitWiki') + "</a> " + window.env.t('unlockFeaturesT2'),
placement: "right"
}
];
@@ -96,7 +96,7 @@ angular.module('guideServices', []).
var showPopover = function(selector, title, html, placement) {
if (!placement) placement = 'bottom';
$(selector).popover('destroy');
var button = "<button class='btn btn-sm btn-default' onClick=\"$('" + selector + "').popover('hide');return false;\">Close</button>";
var button = "<button class='btn btn-sm btn-default' onClick=\"$('" + selector + "').popover('hide');return false;\">" + window.env.t('close') + "</button>";
html = "<div><div class='npc_justin float-left'></div>" + html + '<br/>' + button + '</div>';
$(selector).popover({
title: title,
@@ -109,19 +109,19 @@ angular.module('guideServices', []).
$rootScope.$watch('user.flags.customizationsNotification', function(after, before) {
if (alreadyShown(before, after)) return;
showPopover('.main-herobox', 'Customize Your Avatar', "Click your avatar to customize your appearance.", 'bottom');
showPopover('.main-herobox', window.env.t('customAvatar'), window.env.t('customAvatarText'), 'bottom');
});
$rootScope.$watch('user.flags.itemsEnabled', function(after, before) {
if (alreadyShown(before, after)) return;
var html = "Congratulations, you have unlocked the Item Store! You can now buy weapons, armor, potions, etc. Read each item's comment for more information.";
showPopover('div.rewards', 'Item Store Unlocked', html, 'left');
var html = window.env.t('storeUnlockedText');
showPopover('div.rewards', window.env.t('storeUnlocked'), html, 'left');
});
$rootScope.$watch('user.flags.partyEnabled', function(after, before) {
if (alreadyShown(before, after)) return;
var html = "Be social, join a party and play Habit with your friends! You'll be better at your habits with accountability partners. Click User -> Options -> Party, and follow the instructions. LFG anyone?";
showPopover('.user-menu', 'Party System', html, 'bottom');
var html = window.env.t('partySysText');
showPopover('.user-menu', window.env.t('partySys'), html, 'bottom');
});
$rootScope.$watch('user.flags.dropsEnabled', function(after, before) {
@@ -130,20 +130,12 @@ angular.module('guideServices', []).
if (!eggs) {
eggs['Wolf'] = 1; // This is also set on the server
}
$rootScope.modals.dropsEnabled = true;
});
$rootScope.$watch('user.items.pets', function(after, before) {
if (User.user.achievements && User.user.achievements.beastMaster) return;
if (before >= 90) {
User.set({'achievements.beastMaster': true});
$('#beastmaster-achievement-modal').modal('show'); // FIXME
}
$rootScope.openModal('dropsEnabled');
});
$rootScope.$watch('user.flags.rebirthEnabled', function(after, before) {
if (alreadyShown(before, after)) return;
$rootScope.modals.rebirthEnabled = true;
$rootScope.openModal('rebirthEnabled');
});
@@ -160,8 +152,8 @@ angular.module('guideServices', []).
$timeout(function(){tour.goTo(0)});
}),
element: '.equipment-tab',
title: "Class Gear",
content: "First: don't panic! Your old gear is in your inventory, and you're now wearing your apprentice <strong>" + User.user.stats.class + "</strong> equipment. Wearing your class's gear grants you a 50% bonus to stats. However, feel free to switch back to your old gear."
title: window.env.t('classGear'),
content: window.env.t('classGearText', {klass: User.user.stats.class})
},
{
path: '/#/options/profile/stats',
@@ -169,21 +161,21 @@ angular.module('guideServices', []).
$timeout(function(){tour.goTo(1)});
}),
element: ".allocate-stats",
title: "Stats",
content: "These are your class's stats, they affect the game-play. Each time you level up, you get one point to allocate to particular stat. Hover over each stat for more information.",
title: window.env.t('stats'),
content: window.env.t('classStats'),
}, {
element: ".auto-allocate",
title: "Auto Allocate",
title: window.env.t('autoAllocate'),
placement: 'left',
content: "If 'automatic allocation' is checked, your avatar gains stats automatically based on your tasks' attributes, which you can find in <strong>TASK > Edit > Advanced > Attributes</strong>. Eg, if you hit the gym often, and your 'Gym' Daily is set to 'Physical', you'll gain STR automatically.",
content: window.env.t('autoAllocateText'),
}, {
element: ".meter.mana",
title: "Spells",
content: "You can now unlock class-specific spells. You'll see your first at level 11. Your mana replenishes 10 points per day, plus 1 point per completed <a target='_blank' href='http://habitrpg.wikia.com/wiki/Todos'>To-Do</a>."
title: window.env.t('spells'),
content: window.env.t('spellsText') + " <a target='_blank' href='http://habitrpg.wikia.com/wiki/Todos'>" + window.env.t('toDo') + "</a>."
}, {
orphan: true,
title: "Read More",
content: "For more information on the class-system, see <a href='http://habitrpg.wikia.com/wiki/Class_System' target='_blank'>Wikia</a>."
title: window.env.t('readMore'),
content: window.env.t('moreClass') + " <a href='http://habitrpg.wikia.com/wiki/Class_System' target='_blank'>Wikia</a>."
}
];
_.each(tourSteps, function(step){
+12 -15
View File
@@ -6,11 +6,11 @@ angular.module("notificationServices", [])
function growl(html, type) {
$.bootstrapGrowl(html, {
ele: '#notification-area',
type: type, //(null, 'text', 'error', 'success', 'gp', 'xp', 'hp', 'lvl', 'death', 'mp', 'crit')
type: type || 'warning', //('info', 'text', 'warning', 'success', 'gp', 'xp', 'hp', 'lvl', 'death', 'mp', 'crit')
top_offset: 20,
align: 'right', //('left', 'right', or 'center')
width: 250, //(integer, or 'auto')
delay: 7000,
delay: (type=='error') ? 0 : 7000,
allow_dismiss: true,
stackup_spacing: 10 // spacing between consecutive stacecked growls.
});
@@ -25,11 +25,11 @@ angular.module("notificationServices", [])
gold = Math.floor(absolute);
silver = Math.floor((absolute - gold) * 100);
if (gold && silver > 0) {
return "" + gold + " <i class='icon-gold'></i> " + silver + " <i class='icon-silver'></i>";
return "" + gold + " <span class='notification-icon shop_gold'></span> " + silver + " <span class='notification-icon shop_silver'></span>";
} else if (gold > 0) {
return "" + gold + " <i class='icon-gold'></i>";
return "" + gold + " <span class='notification-icon shop_gold'></span>";
} else if (silver > 0) {
return "" + silver + " <i class='icon-silver'></i>";
return "" + silver + " <span class='notification-icon shop_silver'></span>";
}
};
@@ -45,11 +45,11 @@ angular.module("notificationServices", [])
coins: coins,
hp: function(val) {
// don't show notifications if user dead
growl("<i class='icon-heart'></i> " + sign(val) + " " + round(val) + " HP", 'hp');
growl("<span class='glyphicon glyphicon-heart'></span>&nbsp; " + sign(val) + " " + round(val) + " " + window.env.t('hp'), 'hp');
},
exp: function(val) {
if (val < -50) return; // don't show when they level up (resetting their exp)
growl("<i class='icon-star'></i> " + sign(val) + " " + round(val) + " XP", 'xp');
growl("<span class='glyphicon glyphicon-star'></span>&nbsp; " + sign(val) + " " + round(val) + " " + window.env.t('xp'), 'xp');
},
gp: function(val, bonus) {
growl(sign(val) + " " + coins(val - bonus), 'gp');
@@ -58,22 +58,19 @@ angular.module("notificationServices", [])
growl(val);
},
lvl: function(){
growl('<i class="icon-chevron-up"></i> Level Up!', 'lvl');
},
death: function(){
growl("<i class='icon-death'></i> Respawn!", "death");
growl('<span class="glyphicon glyphicon-chevron-up"></span>&nbsp;' + window.env.t('levelUp'), 'lvl');
},
error: function(error){
growl("<i class='icon-exclamation-sign'></i> " + error, "error");
growl("<span class='glyphicon glyphicon-exclamation-sign'></span>&nbsp; " + error, "danger");
},
mp: function(val) {
growl("<i class='icon-fire'></i> " + sign(val) + " " + round(val) + " MP", 'mp');
growl("<span class='glyphicon glyphicon-fire'></span>&nbsp; " + sign(val) + " " + round(val) + " " + window.env.t('mp'), 'mp');
},
crit: function(val) {
growl("<i class='icon-certificate'></i> Critical Hit! Bonus: " + Math.round(val) + "%", 'crit');
growl("<span class='glyphicon glyphicon-certificate'></span>&nbsp;" + window.env.t('critBonus') + Math.round(val) + "%", 'crit');
},
drop: function(val) {
growl("<i class='icon-gift'></i> " + val, 'drop');
growl("<span class='glyphicon glyphicon-gift'></span>&nbsp; " + val, 'drop');
}
};
}
+19
View File
@@ -5,3 +5,22 @@ window.habitrpgStatic = angular.module('habitrpgStatic', ['notificationServices'
.constant("STORAGE_USER_ID", 'habitrpg-user')
.constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings')
.constant("MOBILE_APP", false)
habitrpgStatic.controller("PlansCtrl", ['$rootScope',
function($rootScope) {
$rootScope.clickContact = function(){
window.ga && ga('send', 'event', 'button', 'click', 'Contact Us (Plans)');
}
}
])
.controller('AboutCtrl',[function(){
$(document).ready(function(){
$('a.gallery').colorbox({
maxWidth: '90%',
maxHeight: '80%',
transition: 'none',
scalePhotos:true
//maxHeight: '70%'
});
});
}])
+14 -13
View File
@@ -1,7 +1,7 @@
{
"app": {
"js": [
"bower_components/jquery/jquery.js",
"bower_components/jquery/dist/jquery.min.js",
"bower_components/jquery.cookie/jquery.cookie.js",
"bower_components/bootstrap-growl/jquery.bootstrap-growl.js",
"bower_components/bootstrap-tour/build/js/bootstrap-tour.js",
@@ -10,7 +10,6 @@
"bower_components/marked/lib/marked.js",
"bower_components/angular-ui-router/release/angular-ui-router.js",
"bower_components/angular-resource/angular-resource.min.js",
"bower_components/angular-ui/build/angular-ui.js",
"bower_components/angular-ui-utils/ui-utils.min.js",
"bower_components/angular-loading-bar/build/loading-bar.js",
"bower_components/Angular-At-Directive/src/at.js",
@@ -21,10 +20,11 @@
"bower_components/ngInfiniteScroll/ng-infinite-scroll.js",
"bower_components/select2/select2.js",
"bower_components/angular-ui-select2/src/select2.js",
"bower_components/JavaScriptButtons/dist/paypal-button.min.js",
"bower_components/bootstrap/docs/assets/js/bootstrap.js",
"bower_components/angular-bootstrap/ui-bootstrap.js",
"bower_components/angular-bootstrap/ui-bootstrap-tpls.js",
"bower_components/bootstrap/dist/js/bootstrap.js",
"bower_components/jquery-ui/ui/minified/jquery.ui.core.min.js",
"bower_components/jquery-ui/ui/minified/jquery.ui.widget.min.js",
@@ -33,6 +33,8 @@
"bower_components/habitrpg-shared/dist/habitrpg-shared.js",
"js/env.js",
"js/app.js",
"js/services/sharedServices.js",
"js/services/authServices.js",
@@ -63,34 +65,33 @@
"js/controllers/hallCtrl.js"
],
"css": [
"bower_components/bootstrap/docs/assets/css/bootstrap.css",
"bower_components/bootstrap/dist/css/bootstrap.css",
"app.css",
"bower_components/habitrpg-shared/dist/habitrpg-shared.css"
]
},
"static": {
"js": [
"bower_components/jquery/jquery.js",
"bower_components/jquery/dist/jquery.min.js",
"bower_components/habitrpg-shared/dist/habitrpg-shared.js",
"bower_components/angular/angular.js",
"bower_components/bootstrap/docs/assets/js/bootstrap.js",
"bower_components/angular-ui/build/angular-ui.js",
"bower_components/angular-bootstrap/ui-bootstrap.js",
"bower_components/angular-bootstrap/ui-bootstrap-tpls.js",
"bower_components/bootstrap/dist/js/bootstrap.js",
"bower_components/jquery-colorbox/jquery.colorbox-min.js",
"bower_components/angular-loading-bar/build/loading-bar.js",
"js/env.js",
"js/static.js",
"js/services/notificationServices.js",
"bower_components/habitrpg-shared/script/userServices.js",
"js/controllers/authCtrl.js"
"js/controllers/authCtrl.js",
"js/controllers/footerCtrl.js"
],
"css": [
"bower_components/bootstrap/docs/assets/css/bootstrap.css",
"bower_components/bootstrap/docs/assets/css/bootstrap-responsive.css",
"bower_components/bootstrap/docs/assets/css/docs.css",
"bower_components/bootstrap/dist/css/bootstrap.css",
"bower_components/jquery-colorbox/example1/colorbox.css",
"static.css"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

+12 -11
View File
@@ -8,6 +8,7 @@ var async = require('async');
var utils = require('../utils');
var nconf = require('nconf');
var User = require('../models/user').model;
var ga = require('./../utils').ga;
var api = module.exports;
@@ -39,7 +40,7 @@ api.authWithSession = function(req, res, next) { //[todo] there is probably a mo
if (!(req.session && req.session.userId))
return res.json(401, NO_SESSION_FOUND);
User.findOne({_id: uid}, function(err, user) {
if (err) return res.json(500, {err: err});
if (err) return next(err);
if (_.isEmpty(user)) return res.json(401, NO_USER_FOUND);
res.locals.user = user;
next();
@@ -88,6 +89,7 @@ api.registerUser = function(req, res, next) {
};
user = new User(newUser);
user.save(cb);
ga.event('register', 'Local').send()
}
], function(err, saved) {
if (err) {
@@ -107,15 +109,15 @@ api.loginLocal = function(req, res, next) {
var password = req.body.password;
if (!(username && password)) return res.json(401, {err:'Missing :username or :password in request body, please provide both'});
User.findOne({'auth.local.username': username}, function(err, user){
if (err) return res.json(500,{err:err});
if (!user) return res.json(401, {err:"Username '" + username + "' not found. Usernames are case-sensitive, click 'Forgot Password' if you can't remember the capitalization."});
if (err) return next(err);
if (!user) return res.json(401, {err:"Username or password incorrect. Click 'Forgot Password' for help with either. (Note: usernames are case-sensitive)"});
// We needed the whole user object first so we can get his salt to encrypt password comparison
User.findOne({
'auth.local.username': username,
'auth.local.hashed_password': utils.encryptPassword(password, user.auth.local.salt)
}, function(err, user){
if (err) return res.json(500,{err:err});
if (!user) return res.json(401,{err:'Incorrect password'});
if (err) return next(err);
if (!user) return res.json(401,{err:"Username or password incorrect. Click 'Forgot Password' for help with either. (Note: usernames are case-sensitive)"});
res.json({id: user._id,token: user.apiToken});
});
});
@@ -164,7 +166,7 @@ api.resetPassword = function(req, res, next){
hashed_password = utils.encryptPassword(newPassword, salt);
User.findOne({'auth.local.email':email}, function(err, user){
if (err) return res.json(500,{err:err});
if (err) return next(err);
if (!user) return res.send(500, {err:"Couldn't find a user registered for email " + email});
user.auth.local.salt = salt;
user.auth.local.hashed_password = hashed_password;
@@ -187,18 +189,18 @@ api.changePassword = function(req, res, next) {
confirmNewPassword = req.body.confirmNewPassword;
if (newPassword != confirmNewPassword)
return res.json(500, {err: "Password & Confirm don't match"});
return res.json(401, {err: "Password & Confirm don't match"});
var salt = user.auth.local.salt,
hashed_old_password = utils.encryptPassword(oldPassword, salt),
hashed_new_password = utils.encryptPassword(newPassword, salt);
if (hashed_old_password !== user.auth.local.hashed_password)
return res.json(500, {err:"Old password doesn't match"});
return res.json(401, {err:"Old password doesn't match"});
user.auth.local.hashed_password = hashed_new_password;
user.save(function(err, saved){
if (err) res.json(500,{err:err});
if (err) next(err);
res.send(200);
})
}
@@ -250,8 +252,7 @@ api.setupPassport = function(router) {
}
});
user.save(cb);
ga.event('register', 'Facebook').send()
}
], function(err, saved){
if (err) return res.redirect('/static/front?err=' + err);
+2 -1
View File
@@ -7,6 +7,7 @@ var shared = require('habitrpg-shared');
var User = require('./../models/user').model;
var Group = require('./../models/group').model;
var Challenge = require('./../models/challenge').model;
var logging = require('./../logging');
var csv = require('express-csv');
var api = module.exports;
@@ -214,7 +215,7 @@ api.update = function(req, res){
// Compare whether any changes have been made to tasks. If so, we'll want to sync those changes to subscribers
if (before.isOutdated(req.body)) {
User.find({_id: {$in: saved.members}}, function(err, users){
console.log('Challenge updated, sync to subscribers');
logging.info('Challenge updated, sync to subscribers');
if (err) throw err;
_.each(users, function(user){
saved.syncToUser(user);
+8
View File
@@ -249,6 +249,14 @@ api.deleteChatMessage = function(req, res){
});
}
api.seenMessage = function(req,res,next){
// Skip the auth step, we want this to be fast. If !found with uuid/token, then it just doesn't save
var update = {$set:{}};
update['$set']['newMessages.'+req.params.gid+'.value'] = false;
User.update({_id:req.headers['x-api-user'], apiToken:req.headers['x-api-key']},update).exec();
res.send(200);
}
api.likeChatMessage = function(req, res, next) {
var user = res.locals.user;
var group = res.locals.group;
+1 -1
View File
@@ -13,7 +13,7 @@ api.ensureAdmin = function(req, res, next) {
}
api.getHeroes = function(req,res,next) {
User.find({'contributor.level':{$ne:null}})// {$exists:true} causes terrible performance http://goo.gl/GCxzC9
User.find({'contributor.level':{$gt:0}})
.select('contributor backer balance profile.name')
.sort('-contributor.level')
.exec(function(err, users){
+66 -37
View File
@@ -10,8 +10,10 @@ var validator = require('validator');
var check = validator.check;
var sanitize = validator.sanitize;
var User = require('./../models/user').model;
var ga = require('./../utils').ga;
var Group = require('./../models/group').model;
var Challenge = require('./../models/challenge').model;
var logging = require('./../logging');
var acceptablePUTPaths;
var api = module.exports;
@@ -33,7 +35,7 @@ api.getContent = function(req, res, next) {
---------------
*/
findTask = function(req, res) {
findTask = function(req, res, next) {
return task = res.locals.user.tasks[req.params.id];
};
@@ -80,17 +82,32 @@ api.score = function(req, res, next) {
var delta = user.ops.score({params:{id:task.id, direction:direction}});
user.save(function(err,saved){
if (err) return res.json(500, {err: err});
if (err) return next(err);
// TODO this should be return {_v,task,stats,_tmp}, instead of merging everything togther at top-level response
// However, this is the most commonly used API route, and changing it will mess with all 3rd party consumers. Bad idea :(
res.json(200, _.extend({
delta: delta,
_tmp: user._tmp
}, saved.toJSON().stats));
});
// if it's a challenge task, sync the score
user.syncScoreToChallenge(task, delta);
// If it's a challenge task, sync the score. Do it in the background, we've already sent down a response
// and the user doesn't care what happens back there
if (!task.challenge || !task.challenge.id || task.challenge.broken) return;
if (task.type == 'reward') return; // we don't want to update the reward GP cost
Challenge.findById(task.challenge.id, 'habits dailys todos rewards', function(err, chal){
if (err) return next(err);
if (!chal) {
task.challenge.broken = 'CHALLENGE_DELETED';
return user.save();
}
var t = chal.tasks[task.id];
if (!t) return chal.syncToUser(user); // this task was removed from the challenge, notify user
t.value += delta;
if (t.type == 'habit' || t.type == 'daily')
t.history.push({value: t.value, date: +new Date});
chal.save();
});
});
};
/**
@@ -192,7 +209,7 @@ api.update = function(req, res, next) {
});
user.save(function(err) {
if (!_.isEmpty(errors)) return res.json(401, {err: errors});
if (err) return res.json(500, {err: err});
if (err) return next(err);
res.json(200, user);
});
};
@@ -232,9 +249,9 @@ api.cron = function(req, res, next) {
// api.reroll // Shared.ops
// api.reset // Shared.ops
api['delete'] = function(req, res) {
api['delete'] = function(req, res, next) {
res.locals.user.remove(function(err){
if (err) return res.json(500,{err:err});
if (err) return next(err);
res.send(200);
})
}
@@ -253,11 +270,11 @@ api['delete'] = function(req, res) {
------------------------------------------------------------------------
*/
api.addTenGems = function(req, res) {
api.addTenGems = function(req, res, next) {
var user = res.locals.user;
user.balance += 2.5;
user.save(function(err){
if (err) return res.json(500,{err:err});
if (err) return next(err);
res.send(204);
})
}
@@ -267,7 +284,7 @@ api.addTenGems = function(req, res) {
/*
Setup Stripe response when posting payment
*/
api.buyGems = function(req, res) {
api.buyGems = function(req, res, next) {
var api_key = nconf.get('STRIPE_API_KEY');
var stripe = require("stripe")(api_key);
var token = req.body.id;
@@ -300,8 +317,12 @@ api.buyGems = function(req, res) {
dateUpdated: new Date,
gemsBought: 0
};
ga.event('subscribe', 'Stripe').send()
ga.transaction(response.id, 5).item(5, 1, "stripe-subscription", "Subscription > Stripe").send()
} else {
user.balance += 5;
ga.event('checkout', 'Stripe').send()
ga.transaction(response.id, 5).item(5, 1, "stripe-checkout", "Gems > Stripe").send()
}
user.save(cb);
}
@@ -311,7 +332,7 @@ api.buyGems = function(req, res) {
});
};
api.cancelSubscription = function(req, res) {
api.cancelSubscription = function(req, res, next) {
var api_key = nconf.get('STRIPE_API_KEY');
var stripe = require("stripe")(api_key);
var user = res.locals.user;
@@ -330,6 +351,7 @@ api.cancelSubscription = function(req, res) {
], function(err, saved){
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
res.send(200, saved);
ga.event('unsubscribe', 'Stripe').send()
});
}
@@ -349,7 +371,9 @@ api.buyGemsPaypalIPN = function(req, res, next) {
user.balance += 5;
//user.purchased.ads = true;
user.save();
console.log('PayPal transaction completed and user updated');
logging.info('PayPal transaction completed and user updated');
ga.event('checkout', 'PayPal').send()
ga.transaction(req.body.txn_id, 5).item(5, 1, "paypal-checkout", "Gems > PayPal").send()
});
}
});
@@ -369,7 +393,7 @@ api.buyGemsPaypalIPN = function(req, res, next) {
Spells
------------------------------------------------------------------------
*/
api.cast = function(req, res) {
api.cast = function(req, res, next) {
var user = res.locals.user;
var targetType = req.query.targetType;
var targetId = req.query.targetId;
@@ -380,7 +404,7 @@ api.cast = function(req, res) {
var done = function(){
var err = arguments[0];
var saved = _.size(arguments == 3) ? arguments[2] : arguments[1];
if (err) return res.json(500, {err:err});
if (err) return next(err);
res.json(saved);
}
@@ -400,7 +424,7 @@ api.cast = function(req, res) {
case 'user':
async.waterfall([
function(cb){
Group.findOne({type: 'party', members: {'$in': [user._id]}}).populate('members', 'profile.name stats achievements').exec(cb);
Group.findOne({type: 'party', members: {'$in': [user._id]}}).populate('members', 'profile.name stats achievements items.special').exec(cb);
},
function(group, cb) {
// Solo player? let's just create a faux group for simpler code
@@ -444,15 +468,15 @@ _.each(shared.wrap({}).ops, function(op,k){
res.locals.user.ops[k](req,function(err, response){
// If we want to send something other than 500, pass err as {code: 200, message: "Not enough GP"}
if (err) {
if (!err.code) return res.json(500,{err:err});
if (!err.code) return next(err);
if (err.code >= 400) return res.json(err.code,{err:err.message});
// In the case of 200s, they're friendly alert messages like "You're pet has hatched!" - still send the op
}
res.locals.user.save(function(err){
if (err) return res.json(500,{err:err});
if (err) return next(err);
res.json(200,response);
})
})
}, ga);
}
}
})
@@ -472,28 +496,33 @@ api.batchUpdate = function(req, res, next) {
var oldSend = res.send;
var oldJson = res.json;
var callOp = function(_req, cb) {
res.send = res.json = function(code, data) {
if (_.isNumber(code) && code >= 500)
return cb(code+": "+ (data.message ? data.message : data.err ? data.err : JSON.stringify(data)));
return cb();
};
api[_req.op](_req, res);
};
// Stash user.save, we'll queue the save op till the end (so we don't overload the server)
var oldSave = user.save;
user.save = function(cb){cb(null,user)}
res.locals.ops = [];
// Setup the array of functions we're going to call in parallel with async
var ops = _.transform(req.body, function(result, _req) {
if (!_.isEmpty(_req)) {
result.push(function(cb) {
res.locals.ops.push(_req);
callOp(_req, cb);
});
}
res.locals.ops = [];
var ops = _.transform(req.body, function(m,_req){
if (_.isEmpty(_req)) return;
m.push(function() {
var cb = arguments[arguments.length-1];
res.locals.ops.push(_req);
res.send = res.json = function(code, data) {
if (_.isNumber(code) && code >= 500)
return cb(code+": "+ (data.message ? data.message : data.err ? data.err : JSON.stringify(data)));
return cb();
};
api[_req.op](_req, res, cb);
});
})
// Finally, save user at the end
.concat(function(){
user.save = oldSave;
user.save(arguments[arguments.length-1]);
});
// call all the operations, then return the user object to the requester
async.series(ops, function(err) {
async.waterfall(ops, function(err,user) {
res.json = oldJson;
res.send = oldSend;
if (err) return next(err);
@@ -518,4 +547,4 @@ api.batchUpdate = function(req, res, next) {
res.json(200, {_v: response._v});
}
});
};
};
+49
View File
@@ -0,0 +1,49 @@
var nconf = require('nconf');
var winston = require('winston');
require('winston-mail').Mail;
require('winston-newrelic');
var logger;
if (logger == null) {
logger = new (winston.Logger)({});
if (nconf.get('NODE_ENV') == 'production') {
logger.add(winston.transports.newrelic, {});
logger.add(winston.transports.Mail, {
to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'),
from: "HabitRPG <" + nconf.get('SMTP_USER') + ">",
subject: "HabitRPG Error",
host: nconf.get('SMTP_HOST'),
port: nconf.get('SMTP_PORT'),
tls: nconf.get('SMTP_TLS'),
username: nconf.get('SMTP_USER'),
password: nconf.get('SMTP_PASS'),
level: 'error'
});
} else {
logger.add(winston.transports.Console, {colorize:true});
logger.add(winston.transports.File, {filename: 'habitrpg.log'});
}
}
// A custom log function that wraps Winston. Makes it easy to instrument code
// and still possible to replace Winston in the future.
module.exports.log = function(/* variable args */) {
if (logger)
logger.log.apply(logger, arguments);
};
module.exports.info = function(/* variable args */) {
if (logger)
logger.info.apply(logger, arguments);
};
module.exports.warn = function(/* variable args */) {
if (logger)
logger.warn.apply(logger, arguments);
};
module.exports.error = function(/* variable args */) {
if (logger)
logger.error.apply(logger, arguments);
};
+66 -27
View File
@@ -4,24 +4,57 @@ var fs = require('fs');
var path = require('path');
var User = require('./models/user').model
var limiter = require('connect-ratelimit');
var logging = require('./logging');
var domainMiddleware = require('domain-middleware');
var cluster = require('cluster');
module.exports.apiThrottle = function(app) {
if (nconf.get('NODE_ENV') !== 'production') return;
app.use(limiter({
end:false,
catagories:{
normal: {
// 2 req/s, but split as minutes
totalRequests: 120,
totalRequests: 80,
every: 60000
}
}
})).use(function(req,res,next){
//console.log(res.ratelimit);
//logging.info(res.ratelimit);
if (res.ratelimit.exceeded) return res.json(429,{err:'Rate limit exceeded'});
next();
});
}
module.exports.domainMiddleware = function(server,mongoose) {
return domainMiddleware({
server: {
close:function(){
server.close();
mongoose.connection.close();
}
},
killTimeout: 10000
});
}
module.exports.errorHandler = function(err, req, res, next) {
//res.locals.domain.emit('error', err);
// when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER)
var stack = (err.stack ? err.stack : err.message ? err.message : err) +
"\n ----------------------------\n" +
"\n\noriginalUrl: " + req.originalUrl +
"\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] +
"\n\nheaders: " + JSON.stringify(req.headers) +
"\n\nbody: " + JSON.stringify(req.body) +
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
logging.error(stack);
var message = err.message ? err.message : err;
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);
}
module.exports.forceSSL = function(req, res, next){
var baseUrl = nconf.get("BASE_URL");
// Note x-forwarded-proto is used by Heroku & nginx, you'll have to do something different if you're not using those
@@ -89,22 +122,21 @@ var getManifestFiles = function(page){
if(!files) throw new Error("Page not found!");
var css = '';
_.each(files.css, function(file){
css += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
});
var code = '';
if(nconf.get('NODE_ENV') === 'production'){
return css + '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(page + '.css') + '">';
code += '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
}else{
var results = css;
_.each(files.js, function(file){
results += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
_.each(files.css, function(file){
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
});
_.each(files.js, function(file){
code += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
});
return results;
}
return code;
}
// Translations
@@ -112,12 +144,12 @@ var getManifestFiles = function(page){
var translations = {};
var loadTranslations = function(locale){
var files = require(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/", locale, 'app.json')).files;
var files = fs.readdirSync(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/", locale));
translations[locale] = {};
_.each(files, function(file){
_.merge(translations[locale], require(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/", locale, file)));
});
}
};
// First fetch english so we can merge with missing strings in other languages
loadTranslations('en');
@@ -164,7 +196,6 @@ var getUserLanguage = function(req, callback){
var acceptable = _(req.acceptedLanguages).map(function(lang){
return lang.slice(0, 2);
}).uniq().value();
var matches = _.intersection(acceptable, langCodes);
return matches.length > 0 ? matches[0] : 'en';
};
@@ -176,44 +207,52 @@ var getUserLanguage = function(req, callback){
return callback(null, _.find(avalaibleLanguages, {code: user.preferences.language}));
}else{
var langCode = getFromBrowser();
if(user && translations[langCode]){
user.preferences.language = langCode;
user.save(); //callback?
}
// Because english is usually always avalaible as an acceptable language for the browser,
// if the user visit the page when his own language is not avalaible yet
// he'll have english set in his preferences, which is not good.
//if(user && translations[langCode]){
//user.preferences.language = langCode;
//user.save(); //callback?
//}
return callback(null, _.find(avalaibleLanguages, {code: langCode}))
}
});
}else{
return callback(null, _.find(avalaibleLanguages, {code: getFromBrowser()}));
return callback(null, _.find(avalaibleLanguages, {code: getFromBrowser()}));
}
}
module.exports.locals = function(req, res, next) {
getUserLanguage(req, function(err, language){
if(err) return res.json(500, {err: err});
if(err) return res.json(500, {err: err});
var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/'
// Load moment.js language file only when not on static pages
language.momentLang = ((!isStaticPage && momentLangs[language.code])|| undefined);
language.momentLang = (momentLangs[language.code] || undefined);
res.locals.habitrpg = {
NODE_ENV: nconf.get('NODE_ENV'),
BASE_URL: nconf.get('BASE_URL'),
PAYPAL_MERCHANT: nconf.get('PAYPAL_MERCHANT'),
GA_ID: nconf.get("GA_ID"),
IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'),
getManifestFiles: getManifestFiles,
getBuildUrl: getBuildUrl,
avalaibleLanguages: avalaibleLanguages,
language: language,
isStaticPage: isStaticPage,
translations: translations[language.code],
t: function(stringName, vars){
var string = translations[language.code][stringName];
if(!string) return _.template(translations[language.code].stringNotFound, {string: stringName});
return vars === undefined ? string : _.template(string, vars);
return vars === undefined ? string : _.template(string, vars);
},
siteVersion: siteVersion
}
next();
next();
});
}
}
-1
View File
@@ -3,7 +3,6 @@ var Schema = mongoose.Schema;
var shared = require('habitrpg-shared');
var _ = require('lodash');
var TaskSchemas = require('./task');
var Group = require('./group').model;
var ChallengeSchema = new Schema({
_id: {type: String, 'default': shared.uuid},
+11 -3
View File
@@ -3,7 +3,6 @@ var Schema = mongoose.Schema;
var shared = require('habitrpg-shared');
var _ = require('lodash');
var async = require('async');
var User = require('./user').model;
var GroupSchema = new Schema({
_id: {type: String, 'default': shared.uuid},
@@ -107,6 +106,17 @@ GroupSchema.methods.sendChat = function(message, user){
}
group.chat.unshift(message);
group.chat.splice(200);
// Kick off chat notifications in the background.
var lastSeenUpdate = {$set:{}, $inc:{_v:1}};
lastSeenUpdate['$set']['newMessages.'+group._id] = {name:group.name,value:true};
if (group._id == 'habitrpg') {
// TODO For Tavern, only notify them if their name was mentioned
// var profileNames = [] // get usernames from regex of @xyz. how to handle space-delimited profile names?
// User.update({'profile.name':{$in:profileNames}},lastSeenUpdate,{multi:true}).exec();
} else {
mongoose.model('User').update({_id:{$in:group.members, $ne: user ? user._id : ''}},lastSeenUpdate,{multi:true}).exec();
}
}
var cleanQuestProgress = function(merge){
@@ -161,8 +171,6 @@ GroupSchema.methods.finishQuest = function(quest, cb) {
})
var members = _.keys(group.quest.members);
group.quest = {};group.markModified('quest');
// FIXME this is TERRIBLE practice. Looks like there are circular dependencies in the models, such that `var User` at
// this point is undefined. So we get around that by loading from mongoose only once we get to this point
mongoose.models.User.update({_id:{$in:members}}, updates, {multi:true}, cb);
}
+17 -22
View File
@@ -42,7 +42,8 @@ var UserSchema = new Schema({
rebirths: Number,
rebirthLevel: Number,
perfect: Number,
habitBirthday: Boolean
habitBirthday: Boolean,
valentine: Number
},
auth: {
facebook: Schema.Types.Mixed,
@@ -68,7 +69,8 @@ var UserSchema = new Schema({
level: Number, // 1-7, see https://trello.com/c/wkFzONhE/277-contributor-gear
admin: Boolean,
text: String, // Artisan, Friend, Blacksmith, etc
contributions: String // a markdown textarea to list their contributions + links
contributions: String, // a markdown textarea to list their contributions + links
critical: String
},
balance: {type: Number, 'default':0},
@@ -135,10 +137,12 @@ var UserSchema = new Schema({
},
special:{
snowball: {type: Number, 'default': 0}
snowball: {type: Number, 'default': 0},
valentine: Number,
valentineReceived: Array // array of strings, by sender name
},
// -------------- Animals -------------------
// -------------- Animals -------------------
// Complex bit here. The result looks like:
// pets: {
// 'Wolf-Desert': 0, // 0 means does not own
@@ -149,6 +153,8 @@ var UserSchema = new Schema({
_.defaults(
// First transform to a 1D eggs/potions mapping
_.transform(shared.content.pets, function(m,v,k){ m[k] = Number; }),
// Then add quest pets
_.transform(shared.content.questPets, function(m,v,k){ m[k] = Number; }),
// Then add additional pets (backer, contributor)
_.transform(shared.content.specialPets, function(m,v,k){ m[k] = Number; })
),
@@ -180,6 +186,8 @@ var UserSchema = new Schema({
mounts: _.defaults(
// First transform to a 1D eggs/potions mapping
_.transform(shared.content.pets, function(m,v,k){ m[k] = Boolean; }),
// Then add quest pets
_.transform(shared.content.questPets, function(m,v,k){ m[k] = Boolean; }),
// Then add additional pets (backer, contributor)
{
'LionCub-Ethereal': Boolean,
@@ -202,9 +210,11 @@ var UserSchema = new Schema({
lastCron: {type: Date, 'default': Date.now},
// {GROUP_ID: Boolean}, represents whether they have unseen chat messages
newMessages: {type: Schema.Types.Mixed, 'default': {}},
party: {
// id // FIXME can we use a populated doc instead of fetching party separate from user?
lastMessageSeen: String,
order: {type:String, 'default':'level'},
quest: {
key: String,
@@ -240,7 +250,8 @@ var UserSchema = new Schema({
disableClasses: {type: Boolean, 'default': false},
newTaskEdit: {type: Boolean, 'default': false},
tagsCollapsed: {type: Boolean, 'default': false},
advancedCollapsed: {type: Boolean, 'default': false}
advancedCollapsed: {type: Boolean, 'default': false},
toolbarCollapsed: {type:Boolean, 'default':false}
},
profile: {
blurb: String,
@@ -362,22 +373,6 @@ UserSchema.pre('save', function(next) {
next();
});
UserSchema.methods.syncScoreToChallenge = function(task, delta){
if (!task.challenge || !task.challenge.id || task.challenge.broken) return;
if (task.type == 'reward') return; // we don't want to update the reward GP cost
var self = this;
Challenge.findById(task.challenge.id, function(err, chal){
if (err) throw err;
var t = chal.tasks[task.id];
if (!t) return chal.syncToUser(self); // this task was removed from the challenge, notify user
t.value += delta;
if (t.type == 'habit' || t.type == 'daily') {
t.history.push({value: t.value, date: +new Date});
}
chal.save();
});
}
UserSchema.methods.unlink = function(options, cb) {
var cid = options.cid, keep = options.keep, tid = options.tid;
var self = this;
+3 -2
View File
@@ -6,6 +6,7 @@ var icalendar = require('icalendar');
var api = require('./../controllers/user');
var auth = require('./../controllers/auth');
var middleware = require('../middleware');
var logging = require('./../logging');
/* ---------- Deprecated API ------------*/
@@ -78,7 +79,7 @@ var batchUpdate = function(req, res, next) {
req.body = action.data;
res.send = res.json = function(code, data) {
if (_.isNumber(code) && code >= 400) {
console.error({
logging.error({
code: code,
data: data
});
@@ -168,4 +169,4 @@ router.get('*', deprecated);
router.post('*', deprecated);
router.put('*', deprecated);
module.exports = router;
module.exports = router;
+11
View File
@@ -488,6 +488,17 @@ module.exports = (swagger, v2) ->
middleware: [auth.auth, groups.attachGroup]
action: groups.postChat
# placing before route below, so that if !=='seen' it goes to next()
"/groups/{gid}/chat/seen":
spec:
method: 'POST'
description: "Flag chat messages for a particular group as seen"
parameters: [
path 'gid','Group id','string'
]
middleware: []
action: groups.seenMessage
"/groups/{gid}/chat/{messageId}":
spec:
method: 'DELETE'
+5 -3
View File
@@ -18,9 +18,7 @@ router.get('/', middleware.locals, function(req, res) {
// -------- Marketing --------
router.get('/static/front', middleware.locals, function(req, res) {
var env = res.locals.habitrpg;
env.isFrontPage = true;
res.render('static/front', {env: env});
res.render('static/front', {env: res.locals.habitrpg});
});
router.get('/static/privacy', middleware.locals, function(req, res) {
@@ -39,6 +37,10 @@ router.get('/static/about', middleware.locals, function(req, res) {
res.render('static/about', {env: res.locals.habitrpg});
});
router.get('/static/videos', middleware.locals, function(req, res) {
res.render('static/videos', {env: res.locals.habitrpg});
});
router.get('/static/contact', middleware.locals, function(req, res) {
res.render('static/contact', {env: res.locals.habitrpg});
});
+3 -2
View File
@@ -1,6 +1,7 @@
require('coffee-script') // for habitrpg-shared
var nconf = require('nconf');
var utils = require('./utils');
var logging = require('./logging');
utils.setupConfig();
var async = require('async');
var mongoose = require('mongoose');
@@ -15,7 +16,7 @@ async.waterfall([
Group.findById('habitrpg', cb);
},
function(tavern, cb){
console.log({tavern:tavern,cb:cb});
logging.info({tavern:tavern,cb:cb});
if (!tavern) {
tavern = new Group({
_id: 'habitrpg',
@@ -31,6 +32,6 @@ async.waterfall([
}
],function(err){
if (err) throw err;
console.log("Done initializing database");
logging.info("Done initializing database");
mongoose.disconnect();
})
+22 -30
View File
@@ -4,19 +4,19 @@ var _ = require('lodash');
var nconf = require('nconf');
var utils = require('./utils');
utils.setupConfig();
var logging = require('./logging');
var isProd = nconf.get('NODE_ENV') === 'production';
var isDev = nconf.get('NODE_ENV') === 'development';
if (cluster.isMaster && (isDev || isProd)) {
// Fork workers.
_.times(require('os').cpus().length, function(){
_.times(_.min([require('os').cpus().length,2]), function(){
cluster.fork();
})
});
cluster.on('exit', function(worker, code, signal) {
cluster.on('disconnect', function(worker, code, signal) {
var w = cluster.fork(); // replace the dead worker
console.error('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid);
logging.info('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid);
});
} else {
@@ -24,7 +24,6 @@ if (cluster.isMaster && (isDev || isProd)) {
var express = require("express");
var http = require("http");
var path = require("path");
var domainMiddleware = require('domain-middleware');
var swagger = require("swagger-node-express");
var middleware = require('./middleware');
@@ -35,14 +34,18 @@ if (cluster.isMaster && (isDev || isProd)) {
// ------------ MongoDB Configuration ------------
mongoose = require('mongoose');
require('./models/user'); //load up the user schema - TODO is this necessary?
require('./models/group');
require('./models/challenge');
mongoose.connect(nconf.get('NODE_DB_URI'), {auto_reconnect:true}, function(err) {
if (err) throw err;
console.info('Connected with Mongoose');
var mongooseOptions = !isProd ? {} : {
replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } }
};
mongoose.connect(nconf.get('NODE_DB_URI'), mongooseOptions, function(err) {
if (err) throw err;
logging.info('Connected with Mongoose');
});
// load schemas & models
require('./models/challenge');
require('./models/group');
require('./models/user');
// ------------ Passport Configuration ------------
var passport = require('passport')
@@ -63,7 +66,6 @@ if (cluster.isMaster && (isDev || isProd)) {
done(null, obj);
});
// Use the FacebookStrategy within Passport.
// Strategies in Passport require a `verify` function, which accept
// credentials (in this case, an accessToken, refreshToken, and Facebook
@@ -88,14 +90,9 @@ if (cluster.isMaster && (isDev || isProd)) {
// ------------ Server Configuration ------------
domainMiddleware({
server: server,
killTimeout: 3000
}),
app.set("port", nconf.get('PORT'));
middleware.apiThrottle(app);
app.use(middleware.domainMiddleware(server,mongoose));
if (!isProd) app.use(express.logger("dev"));
app.use(express.compress());
app.set("views", __dirname + "/../views");
@@ -119,14 +116,11 @@ if (cluster.isMaster && (isDev || isProd)) {
app.use(app.router);
var maxAge = isProd ? 31536000000 : 0;
// Cache emojis without copying them to build, they are too many
app.use(express['static'](path.join(__dirname, "/../build"), { maxAge: maxAge }));
app.use('/bower_components/habitrpg-shared/img/emoji/unicode', express['static'](path.join(__dirname, "/../public/bower_components/habitrpg-shared/img/emoji/unicode"), { maxAge: maxAge }));
app.use(express['static'](path.join(__dirname, "/../public")));
// development only
//if ("development" === app.get("env")) {
// app.use(express.errorHandler());
//}
// Custom Directives
app.use(require('./routes/pages').middleware);
app.use(require('./routes/auth').middleware);
@@ -134,15 +128,13 @@ if (cluster.isMaster && (isDev || isProd)) {
app.use('/api/v2', v2);
app.use('/api/v1', require('./routes/apiv1').middleware);
app.use('/export', require('./routes/dataexport').middleware);
app.use(utils.errorHandler);
require('./routes/apiv2.coffee')(swagger, v2);
app.use(middleware.errorHandler);
server.on('request', app);
server.listen(app.get("port"), function() {
return console.log("Express server listening on port " + app.get("port"));
return logging.info("Express server listening on port " + app.get("port"));
});
module.exports = server;
}
}
+10 -41
View File
@@ -3,6 +3,8 @@ var nconf = require('nconf');
var crypto = require('crypto');
var path = require("path");
module.exports.ga = undefined; // set Google Analytics on nconf init
module.exports.sendEmail = function(mailData) {
var smtpTransport = nodemailer.createTransport("SMTP",{
service: nconf.get('SMTP_SERVICE'),
@@ -12,8 +14,9 @@ module.exports.sendEmail = function(mailData) {
}
});
smtpTransport.sendMail(mailData, function(error, response){
if(error) console.log(error);
else console.log("Message sent: " + response.message);
var logging = require('./logging');
if(error) logging.error(error);
else logging.info("Message sent: " + response.message);
smtpTransport.close(); // shut down the connection pool, no more messages
});
}
@@ -31,8 +34,6 @@ module.exports.makeSalt = function() {
return crypto.randomBytes(Math.ceil(len / 2)).toString('hex').substring(0, len);
}
/**
* Load nconf and define default configuration values if config.json or ENV vars are not found
*/
@@ -42,42 +43,10 @@ module.exports.setupConfig = function(){
//.file('defaults', path.join(path.resolve(__dirname, '../config.json.example')))
.file('user', path.join(path.resolve(__dirname, '../config.json')));
// var agent;
// if (process.env.NODE_ENV === 'development') {
// // Follow these instructions for profiling / debugging leaks
// // * https://developers.google.com/chrome-developer-tools/docs/heap-profiling
// // * https://developers.google.com/chrome-developer-tools/docs/memory-analysis-101
// agent = require('webkit-devtools-agent');
// console.log("To debug memory leaks:" +
// "\n\t(1) Run `kill -SIGUSR2 " + process.pid + "`" +
// "\n\t(2) open http://c4milo.github.com/node-webkit-agent/21.0.1180.57/inspector.html?host=localhost:1337&page=0");
// }
if (nconf.get('NODE_ENV') === "development") {
if (nconf.get('NODE_ENV') === "development")
Error.stackTraceLimit = Infinity;
}
if (nconf.get('NODE_ENV') === 'production') require('newrelic');
};
if (nconf.get('NODE_ENV') === 'production')
require('newrelic');
module.exports.errorHandler = function(err, req, res, next) {
// when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER)
var stack = (err.stack ? err.stack : err.message ? err.message : err) +
"\n ----------------------------\n" +
"\n\noriginalUrl: " + req.originalUrl +
"\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] +
"\n\nheaders: " + JSON.stringify(req.headers) +
"\n\nbody: " + JSON.stringify(req.body) +
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
module.exports.sendEmail({
from: "HabitRPG <" + nconf.get('SMTP_USER') + ">",
to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'),
subject: "HabitRPG Error",
text: stack
});
console.error(stack);
var message = err.message ? err.message : err;
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);
process.exit(0);
}
module.exports.ga = require('universal-analytics')(nconf.get('GA_ID'));
};
+27
View File
@@ -253,6 +253,33 @@ describe('API', function () {
});
});
it('Challenge deleted, breaks task link', function (done) {
var itThis = this;
request.del(baseURL + "/challenges/" + challenge._id).end(function (res) {
User.findById(user._id, function(err,user){
var len = user.dailys.length- 1,
daily = user.dailys[user.dailys.length-1];
expect(daily.challenge.broken).to.be('CHALLENGE_DELETED');
// Now let's handle if challenge was deleted, but didn't get to update all the users (an error)
var unset = {$unset:{}};
unset['$unset']['dailys.' + len + '.challenge.broken'] = 1;
User.findByIdAndUpdate(user._id,unset,function(err,user){
expect(err).to.not.be.ok();
expect(user.dailys[len].challenge.broken).to.not.be.ok();
request.post(baseURL + "/user/tasks/" + daily.id + "/up").end(function (res) {
setTimeout(function(){
User.findById(user._id,function(err,user){
expect(user.dailys[len].challenge.broken).to.be('CHALLENGE_DELETED');
done()
})
}, 100); // we need to wait for challenge to update user, it's a background job for perf reasons
})
})
})
})
});
it('Admin creates a challenge', function (done) {
User.findByIdAndUpdate(_id, {$set:{'contributor.admin':true}}, function (err,_user) {
expect(err).to.not.be.ok();
+17 -26
View File
@@ -3,43 +3,34 @@ doctype 5
html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":applyingAction}', ui-keyup="{27:'castCancel()'}")
head
title=env.t('titleIndex')
// ?v=1 needed to force refresh
link(rel='shortcut icon', href='#{env.getBuildUrl("favicon.ico")}?v=2')
link(rel='shortcut icon', href='#{env.getBuildUrl("favicon.ico")}?v=3')
meta(charset='utf-8')
meta(name='viewport', content='width=device-width, initial-scale=1.0')
meta(name='apple-mobile-web-app-capable', content='yes')
script(type='text/javascript').
window.env = !{JSON.stringify(env)};
style.
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none;
}
!= env.getManifestFiles("app")
//webfonts
link(href='//fonts.googleapis.com/css?family=Lato:300,400,700,400italic,700italic', rel='stylesheet', type='text/css')
meta(name='viewport', content='width=device-width')
meta(name='apple-mobile-web-app-capable', content='yes')
body(ng-cloak)
div(ng-controller='GroupsCtrl')
include ./shared/modals/index
include ./shared/header/header
include ./shared/tasks/lists
include ./main/index
include ./options/index
#notification-area(ng-controller='NotificationCtrl')
#wrap
//if they hide the header, we still need user-menu visible
div(ng-if='user.preferences.hideHeader')
include ./shared/header/menu
.exp-chart(ng-show='charts.exp')
body(ng-cloak, ng-controller='GroupsCtrl')
include ./shared/header/menu
include ./shared/modals/index
include ./shared/header/header
include ./shared/tasks/lists
include ./main/index
include ./options/index
#notification-area(ng-controller='NotificationCtrl')
#wrap.container-fluid
.row
.col-md-12.exp-chart(ng-show='charts.exp')
#main(ui-view)
include ./shared/footer
include ./shared/footer
+29 -26
View File
@@ -1,26 +1,29 @@
.filters.tag-list(ng-controller='FiltersCtrl')
.pull-left.filter-description
=env.t('tags')
|:
ul.nav.nav-pills
li
a(rel='tooltip', title=env.t('editTags'), ng-click='saveOrEdit()')
i(ng-class='{"icon-ok": _editing, "icon-pencil": !_editing}')
li(bindonce='user.tags', ng-class='{active: user.filters[tag.id]}', ng-repeat='tag in user.tags', style='position: relative;')
.input-append.option-group.tag-editing(ng-show='_editing')
input.input.input-small.option-content.tag-editing-pill(type='text', ng-model='tag.name')
span.add-on.tag-editing-pill
a.pull-right(ng-click='user.ops.deleteTag({params:{id:tag.id}})')
i.icon-trash
a(ng-hide='_editing', ng-click='toggleFilter(tag)')
i.icon-bullhorn(bo-if="tag.challenge")
markdown(ng-model='tag.name',style='display: inline-block')
li
form.form-inline(ng-show='_editing', ng-submit='createTag(_newTag)')
.input-append.tag-editing
input.span2.tag-editing-pill(type='text', ng-model='_newTag', placeholder=env.t('newTag'))
button.add-on.tag-editing-pill(type='submit')=env.t('add')
// <li class="{#unless activeFilters(users[_userId].filters)}hidden{/}">
li
a(rel='tooltip', title=env.t('clearFilters'), ng-click='user.filters = {}')
i.icon-remove-sign
.container-fluid
.row
.filters.col-md-12(ng-controller='FiltersCtrl')
.pull-left.filter-description
=env.t('tags')
|:
ul.nav.nav-pills
li
a(rel='tooltip', title=env.t('editTags'), ng-click='saveOrEdit()')
span.glyphicon(ng-class='{"glyphicon-ok": _editing, "glyphicon-pencil": !_editing}')
li(bindonce='user.tags', ng-class='{active: user.filters[tag.id]}', ng-repeat='tag in user.tags')
.input-group(ng-show='_editing')
input.form-control.input-sm(type='text', ng-model='tag.name')
span.input-group-addon
a(ng-click='user.ops.deleteTag({params:{id:tag.id}})')
span.glyphicon.glyphicon-trash
a(ng-hide='_editing', ng-click='toggleFilter(tag)')
span.glyphicon.glyphicon-bullhorn(bo-if="tag.challenge")
markdown(ng-model='tag.name')
li
form.form-inline(ng-show='_editing', ng-submit='createTag(_newTag)')
.input-group
input.form-control.input-sm(type='text', ng-model='_newTag', placeholder=env.t('newTag'))
span.input-group-addon(ng-click='createTag(_newTag)')
=env.t('add')
// <li class="{#unless activeFilters(users[_userId].filters)}hidden{/}">
li
a(rel='tooltip', title=env.t('clearFilters'), ng-click='user.filters = {}')
span.glyphicon.glyphicon-remove-sign
+29 -24
View File
@@ -4,29 +4,34 @@ include ./inventory/index
include ./settings
script(id='partials/options.html', type="text/ng-template")
.grid
.module.full-width
span.option-box.pull-right.wallet
include ../shared/gems
.container-fluid
.row
.col-md-12
span.option-box.pull-right.wallet
include ../shared/gems
ul.nav.nav-tabs
li(ng-class="{ active: $state.includes('options.profile') }")
a(ui-sref='options.profile')
i.icon-user
=env.t('profile')
li(ng-class="{ active: $state.includes('options.social') }")
a(ui-sref='options.social')
i.icon-heart
=env.t('social')
li(ng-class="{ active: $state.includes('options.inventory') }", ng-if='user.flags.dropsEnabled')
a(ui-sref='options.inventory')
i.icon-gift
=env.t('inventory')
li(ng-class="{ active: $state.includes('options.settings') }")
a(ui-sref='options.settings')
i.icon-wrench
=env.t('settings')
ul.nav.nav-tabs
li(ng-class="{ active: $state.includes('options.profile') }")
a(ui-sref='options.profile')
span.glyphicon.glyphicon-user
| &nbsp;
=env.t('profile')
li(ng-class="{ active: $state.includes('options.social') }")
a(ui-sref='options.social')
span.glyphicon.glyphicon-heart
| &nbsp;
=env.t('social')
li(ng-class="{ active: $state.includes('options.inventory') }", ng-if='user.flags.dropsEnabled')
a(ui-sref='options.inventory')
span.glyphicon.glyphicon-gift
| &nbsp;
=env.t('inventory')
li(ng-class="{ active: $state.includes('options.settings') }")
a(ui-sref='options.settings')
span.glyphicon.glyphicon-wrench
| &nbsp;
=env.t('settings')
.tab-content
.tab-pane.active
div(ui-view)
.tab-content
.tab-pane.active
div(ui-view)
+145 -129
View File
@@ -1,145 +1,161 @@
script(type='text/ng-template', id='partials/options.inventory.equipment.html')
.row-fluid
.span6.border-right
h3=env.t('battleGear')
small=env.t('battleGearText')
li.customize-menu.inventory-gear
menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {base:"Base", warrior:"Warrior", wizard:"Mage", rogue:"Rogue", healer:"Healer", special:"Special"}', ng-show='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:{key:item.key}})', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}')
.span6
h3=env.t('costume')
small=env.t('costumeText')
br
label.checkbox.inline
input(type="checkbox", ng-model="user.preferences.costume", ng-click='set({"preferences.costume":!user.preferences.costume})')
=env.t('useCostume')
li.customize-menu(ng-if='user.preferences.costume')
menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {base:"Base", warrior:"Warrior", wizard:"Mage", rogue:"Rogue", healer:"Healer", special:"Special"}', ng-show='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}')
.container-fluid
.row
.col-md-6.border-right
h3.equipment-title=env.t('battleGear')
&nbsp;
.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('battleGearText'))
li.customize-menu.inventory-gear
menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {base:"Base", warrior:"Warrior", wizard:"Mage", rogue:"Rogue", healer:"Healer", special:"Special"}', ng-show='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:{key:item.key}})', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}')
.col-md-6
h3.equipment-title=env.t('costume')
&nbsp;
i.icon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('costumeText'))
.checkbox
label
input(type="checkbox", ng-model="user.preferences.costume", ng-click='set({"preferences.costume":!user.preferences.costume})')
|&nbsp;
=env.t('useCostume')
li.customize-menu(ng-if='user.preferences.costume')
menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {base:"Base", warrior:"Warrior", wizard:"Mage", rogue:"Rogue", healer:"Healer", special:"Special"}', ng-show='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}')
script(type='text/ng-template', id='partials/options.inventory.drops.html')
.row-fluid
.span6.border-right
h2=env.t('inventory')
p.well=env.t('inventoryText')
menu.inventory-list(type='list')
li.customize-menu
menu.pets-menu(label=(env.t('eggs') + ' ({{eggCount}})'))
p.muted(ng-show='eggCount < 1')=env.t('noEggs')
div(ng-repeat='(egg,points) in ownedItems(user.items.eggs)')
//TODO move positioning this styling to css
button.customize-option(popover='{{Content.eggs[egg].notes}}', popover-title='{{Content.eggs[egg].text}} Egg', popover-trigger='mouseenter', popover-placement='right', ng-click='chooseEgg(egg)', class='Pet_Egg_{{egg}}', ng-class='{selectableInventory: selectedPotion && !(user.items.pets[egg+"-"+selectedPotion.key]>0)}')
.badge.badge-info.stack-count {{points}}
//-p {{Content.eggs[egg].text}}
li.customize-menu
menu.hatchingPotion-menu(label=(env.t('hatchingPotions') + ' ({{potCount}})'))
p.muted(ng-show='potCount < 1')=env.t('noHatchingPotions')
div(ng-repeat='(pot,points) in ownedItems(user.items.hatchingPotions)')
button.customize-option(popover='{{Content.hatchingPotions[pot].notes}}', popover-title='{{Content.hatchingPotions[pot].text}} Potion', popover-trigger='mouseenter', popover-placement='right', ng-click='choosePotion(pot)', class='Pet_HatchingPotion_{{pot}}', ng-class='{selectableInventory: selectedEgg && !(user.items.pets[selectedEgg.key+"-"+pot]>0)}')
.badge.badge-info.stack-count {{points}}
li.customize-menu
menu.pets-menu(label=(env.t('scrolls') + ' ({{questCount}})'))
p.muted(ng-show='questCount < 1')=env.t('noScrolls')
p.muted!=env.t('scrollsText1') + ' <a href="/#/options/groups/party">' + env.t('scrollsText2') + '</a>'
div(ng-repeat='(quest_key,points) in ownedItems(user.items.quests)', ng-init='quest = Content.quests[quest_key]')
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='showQuest(quest_key)', class='inventory_quest_scroll', ng-class='{locked: quest.previous && !user.achievements.quests[quest.previous]}')
.badge.badge-info.stack-count {{points}}
li.customize-menu
menu.pets-menu(label=env.t('food') + ' ({{foodCount}})')
p(ng-show='foodCount < 1')=env.t('noFood')
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}}')
.badge.badge-info.stack-count {{points}}
li.customize-menu(ng-if='user.items.special.snowball')
menu.pets-menu(label=env.t('special'))
div
button.customize-option(popover='{{Content.special.snowball.notes}}', popover-title='{{Content.special.snowball.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='castStart(Content.special.snowball)', class='inventory_special_snowball')
.badge.badge-info.stack-count {{user.items.special.snowball}}
.span6
h2=env.t('market')
.row-fluid
table.npc_alex_container
tr
td
.npc_alex.pull-left
td
.popover.static-popover.fade.right.in
.arrow
h3.popover-title
a(target='_blank', href='http://www.kickstarter.com/profile/523661924')=env.t('alexander')
.popover-content
p=env.t('welcomeMarket')
p
button.btn.btn-primary(ng-show='selectedEgg', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedEgg.text}}", gold: "{{selectedEgg.value}}"})
button.btn.btn-primary(ng-show='selectedPotion', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedPotion.text}}", gold: "{{selectedPotion.value}}"})
button.btn.btn-primary(ng-show='selectedFood', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedFood.text}}", gold: "{{selectedFood.value}}"})
.container-fluid
.row
.col-md-6.border-right
h2=env.t('inventory')
p.well=env.t('inventoryText')
menu.inventory-list(type='list')
li.customize-menu
menu.pets-menu(label=env.t('eggs'))
div(ng-repeat='egg in Content.eggs', ng-if='egg.canBuy')
button.customize-option(popover='{{egg.notes}}', popover-title='{{egg.text}} Egg', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("eggs", egg)', class='Pet_Egg_{{egg.key}}')
p
| {{egg.value}}
span.Pet_Currency_Gem1x.inline-gems
li.customize-menu
menu.pets-menu(label=env.t('hatchingPotions'))
div(ng-repeat='pot in Content.hatchingPotions')
button.customize-option(popover='{{pot.notes}}', popover-title='{{pot.text}} Potion', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("hatchingPotions", pot)', class='Pet_HatchingPotion_{{pot.key}}')
p
| {{pot.value}}
span.Pet_Currency_Gem1x.inline-gems
menu.pets-menu(label=(env.t('eggs') + ' ({{eggCount}})'))
p.muted(ng-show='eggCount < 1')=env.t('noEggs')
div(ng-repeat='(egg,points) in ownedItems(user.items.eggs)')
//TODO move positioning this styling to css
button.customize-option(popover='{{Content.eggs[egg].notes}}', popover-title='{{Content.eggs[egg].text}} Egg', popover-trigger='mouseenter', popover-placement='right', ng-click='chooseEgg(egg)', class='Pet_Egg_{{egg}}', ng-class='{selectableInventory: selectedPotion && !(user.items.pets[egg+"-"+selectedPotion.key]>0)}')
.badge.badge-info.stack-count {{points}}
//-p {{Content.eggs[egg].text}}
li.customize-menu
menu.pets-menu(label=env.t('food'))
div(ng-repeat='food in Content.food', ng-if='food.key !== "Saddle" && food.canBuy')
button.customize-option(popover='{{food.notes}}', popover-title='{{food.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("food", food)', class='Pet_Food_{{food.key}}')
p
| {{food.value}}
span.Pet_Currency_Gem1x.inline-gems
menu.hatchingPotion-menu(label=(env.t('hatchingPotions') + ' ({{potCount}})'))
p.muted(ng-show='potCount < 1')=env.t('noHatchingPotions')
div(ng-repeat='(pot,points) in ownedItems(user.items.hatchingPotions)')
button.customize-option(popover='{{Content.hatchingPotions[pot].notes}}', popover-title='{{Content.hatchingPotions[pot].text}} Potion', popover-trigger='mouseenter', popover-placement='right', ng-click='choosePotion(pot)', class='Pet_HatchingPotion_{{pot}}', ng-class='{selectableInventory: selectedEgg && !(user.items.pets[selectedEgg.key+"-"+pot]>0)}')
.badge.badge-info.stack-count {{points}}
li.customize-menu
menu.pets-menu(label=env.t('quests'))
menu.pets-menu(label=(env.t('scrolls') + ' ({{questCount}})'))
p.muted(ng-show='questCount < 1')=env.t('noScrolls')
p.muted!=env.t('scrollsText1') + ' <a href="/#/options/groups/party">' + env.t('scrollsText2') + '</a>'
div(ng-repeat='quest in Content.quests', ng-if='quest.canBuy')
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='left', 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
div(ng-repeat='(quest_key,points) in ownedItems(user.items.quests)', ng-init='quest = Content.quests[quest_key]')
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='showQuest(quest_key)', class='inventory_quest_scroll', ng-class='{locked: quest.previous && !user.achievements.quests[quest.previous]}')
.badge.badge-info.stack-count {{points}}
li.customize-menu
menu.pets-menu(label=env.t('food') + ' ({{foodCount}})')
p(ng-show='foodCount < 1')=env.t('noFood')
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}}')
.badge.badge-info.stack-count {{points}}
li.customize-menu(ng-if='user.items.special.snowball')
menu.pets-menu(label=env.t('special'))
div
button.customize-option(popover='{{Content.food.Saddle.notes}}', popover-title='{{Content.food.Saddle.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("food", Content.food.Saddle)', class='Pet_Food_{{Content.food.Saddle.key}}')
p
| {{Content.food.Saddle.value}}
span.Pet_Currency_Gem1x.inline-gems
button.customize-option(popover='{{Content.special.snowball.notes}}', popover-title='{{Content.special.snowball.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='castStart(Content.special.snowball)', class='inventory_special_snowball')
.badge.badge-info.stack-count {{user.items.special.snowball}}
li.customize-menu(ng-if='user.items.special.valentineReceived[0]')
menu.pets-menu(label=env.t('valentineCard'))
div
button.customize-option(popover=env.t('fortifyPop'), popover-title=env.t('fortifyName'), popover-trigger='mouseenter', popover-placement='left', ng-click='modals.reroll = true', class='inventory_special_fortify')
p
| 4
span.Pet_Currency_Gem1x.inline-gems
//-div
button.customize-option(popover='{{Content.spells.special.snowball.notes}}', popover-title='{{Content.spells.special.snowball.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("special", Content.spells.special.snowball)', class='inventory_special_snowball')
p
| {{Content.spells.special.snowball.value}}
span.Pet_Currency_Gem1x.inline-gems
div(ng-show='user.flags.rebirthEnabled')
button.customize-option(popover=env.t('rebirthPop'), popover-title=env.t('rebirthName'), popover-trigger='mouseenter', popover-placement='left', ng-click='modals.rebirth = true', class='rebirth_orb')
p
| 8
span.Pet_Currency_Gem1x.inline-gems
div(ng-if='user.purchased.plan.planId')
button.customize-option(ng-click='user.ops.purchase({params:{type:"gems",key:"gem"}})')
span.Pet_Currency_Gem.inline-gems
p
| 20
span.shop_gold
button.customize-option(popover="Valentine's Day Card from {{User.user.items.special.valentineReceived[0]}}", popover-trigger='mouseenter', popover-placement='right', ng-click='openModal("valentine")', class='inventory_special_valentine')
.badge.badge-info.stack-count {{user.items.special.valentineReceived.length}}
li.customize-menu(ng-if='user.purchased.plan.customerId')
menu.pets-menu(label=env.t('subscriberItem'))
div
button.customize-option(popover=env.t('subscriberItemText'), popover-trigger='mouseenter', popover-placement='right', class='inventory_present')
.col-md-6
h2=env.t('market')
.row-fluid
table.npc_alex_container
tr
td
.npc_alex.pull-left
td
.popover.static-popover.fade.right.in
.arrow
h3.popover-title
a(target='_blank', href='http://www.kickstarter.com/profile/523661924')=env.t('alexander')
.popover-content
p=env.t('welcomeMarket')
p
button.btn.btn-primary(ng-show='selectedEgg', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedEgg.text}}", gold: "{{selectedEgg.value}}"})
button.btn.btn-primary(ng-show='selectedPotion', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedPotion.text}}", gold: "{{selectedPotion.value}}"})
button.btn.btn-primary(ng-show='selectedFood', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedFood.text}}", gold: "{{selectedFood.value}}"})
menu.inventory-list(type='list')
li.customize-menu
menu.pets-menu(label=env.t('eggs'))
div(ng-repeat='egg in Content.eggs', ng-if='egg.canBuy')
button.customize-option(popover='{{egg.notes}}', popover-title='{{egg.text}} Egg', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("eggs", egg)', class='Pet_Egg_{{egg.key}}')
p
| {{egg.value}}
span.Pet_Currency_Gem1x.inline-gems
li.customize-menu
menu.pets-menu(label=env.t('hatchingPotions'))
div(ng-repeat='pot in Content.hatchingPotions')
button.customize-option(popover='{{pot.notes}}', popover-title='{{pot.text}} Potion', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("hatchingPotions", pot)', class='Pet_HatchingPotion_{{pot.key}}')
p
| {{pot.value}}
span.Pet_Currency_Gem1x.inline-gems
li.customize-menu
menu.pets-menu(label=env.t('food'))
div(ng-repeat='food in Content.food', ng-if='food.key !== "Saddle" && food.canBuy')
button.customize-option(popover='{{food.notes}}', popover-title='{{food.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("food", food)', class='Pet_Food_{{food.key}}')
p
| {{food.value}}
span.Pet_Currency_Gem1x.inline-gems
li.customize-menu
menu.pets-menu(label=env.t('quests'))
p.muted!=env.t('scrollsText1') + ' <a href="/#/options/groups/party">' + env.t('scrollsText2') + '</a>'
div(ng-repeat='quest in Content.quests', ng-if='quest.canBuy')
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='left', 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
li.customize-menu
menu.pets-menu(label=env.t('special'))
div
button.customize-option(popover='{{Content.food.Saddle.notes}}', popover-title='{{Content.food.Saddle.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("food", Content.food.Saddle)', class='Pet_Food_{{Content.food.Saddle.key}}')
p
| {{Content.food.Saddle.value}}
span.Pet_Currency_Gem1x.inline-gems
div
button.customize-option(popover=env.t('fortifyPop'), popover-title=env.t('fortifyName'), popover-trigger='mouseenter', popover-placement='left', ng-click='openModal("reroll")', class='inventory_special_fortify')
p
| 4
span.Pet_Currency_Gem1x.inline-gems
//-div
button.customize-option(popover='{{Content.spells.special.snowball.notes}}', popover-title='{{Content.spells.special.snowball.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("special", Content.spells.special.snowball)', class='inventory_special_snowball')
p
| {{Content.spells.special.snowball.value}}
span.Pet_Currency_Gem1x.inline-gems
div(ng-show='user.flags.rebirthEnabled')
button.customize-option(popover=env.t('rebirthPop'), popover-title=env.t('rebirthName'), popover-trigger='mouseenter', popover-placement='left', ng-click='openModal("rebirth")', class='rebirth_orb')
p
| 8
span.Pet_Currency_Gem1x.inline-gems
div(ng-if='user.purchased.plan.planId')
button.customize-option(ng-click='user.ops.purchase({params:{type:"gems",key:"gem"}})')
span.Pet_Currency_Gem.inline-gems
p
| 20
span.shop_gold
+92 -78
View File
@@ -1,85 +1,99 @@
script(type='text/ng-template', id='partials/options.inventory.mounts.html')
.stable
table(style='width:100%')
tr
td(style='width:144px')
.npc_matt
td
.popover.static-popover.fade.right.in(style='max-width: 550px; margin-left: 10px;')
.arrow
h3.popover-title
a(target='_blank', href='http://www.kickstarter.com/profile/mattboch')=env.t('mattBoch')
.popover-content
p=env.t('mattShall', {name: "{{user.profile.name}}"})
h4= '{{mountCount}} / {{totalPets}} ' + env.t('mountsTamed')
menu.pets(type='list')
li.customize-menu(ng-repeat='egg in Content.eggs')
.container-fluid
.stable.row
.col-md-2
.npc_matt
.col-md-10
.popover.static-popover.fade.right.in
.arrow
h3.popover-title
a(target='_blank', href='http://www.kickstarter.com/profile/mattboch')=env.t('mattBoch')
.popover-content
p=env.t('mattShall', {name: "{{user.profile.name}}"})
h4= '{{Shared.countMounts(null,User.user.items.mounts) || 0}} / {{totalMounts}} ' + env.t('mountsTamed')
.col-md-12
menu.pets(type='list')
li.customize-menu(ng-repeat='egg in Content.eggs')
menu
div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.mountText}}', popover-placement='bottom', ng-init='mount = egg.key+"-"+potion.key')
button(class="pet-button Mount_Head_{{mount}}", ng-show='user.items.mounts[mount]', ng-class='{active: user.items.currentMount == mount}', ng-click='chooseMount(egg.key, potion.key)')
//div(class='Mount_Head_{{mount}}')
button(class="pet-button pet-not-owned", ng-hide='user.items.mounts[mount]')
.PixelPaw
.col-md-12
h4=env.t('rareMounts')
menu
div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.mountText}}', popover-placement='bottom', ng-init='mount = egg.key+"-"+potion.key')
button(class="pet-button Mount_Head_{{mount}}", ng-show='user.items.mounts[mount]', ng-class='{active: user.items.currentMount == mount}', ng-click='chooseMount(egg.key, potion.key)')
//div(class='Mount_Head_{{mount}}')
button(class="pet-button pet-not-owned", ng-hide='user.items.mounts[mount]')
.PixelPaw
h4=env.t('rareMounts')
menu
div
button(ng-if='user.items.mounts["BearCub-Polar"]', class="pet-button Mount_Head_BearCub-Polar", ng-class='{active: user.items.currentMount == "BearCub-Polar"}', ng-click='chooseMount("BearCub", "Polar")', popover=env.t('polarBear'), popover-trigger='mouseenter', popover-placement='bottom')
//.Mount_Head_BearCub-Polar
button(ng-if='user.items.mounts["LionCub-Ethereal"]', class="pet-button Mount_Head_LionCub-Ethereal", ng-class='{active: user.items.currentMount == "LionCub-Ethereal"}', ng-click='chooseMount("LionCub", "Ethereal")', popover=env.t('etherealLion'), popover-trigger='mouseenter', popover-placement='bottom')
//.Mount_Head_LionCub-Ethereal
div
button(ng-if='user.items.mounts["BearCub-Polar"]', class="pet-button Mount_Head_BearCub-Polar", ng-class='{active: user.items.currentMount == "BearCub-Polar"}', ng-click='chooseMount("BearCub", "Polar")', popover=env.t('polarBear'), popover-trigger='mouseenter', popover-placement='bottom')
//.Mount_Head_BearCub-Polar
button(ng-if='user.items.mounts["LionCub-Ethereal"]', class="pet-button Mount_Head_LionCub-Ethereal", ng-class='{active: user.items.currentMount == "LionCub-Ethereal"}', ng-click='chooseMount("LionCub", "Ethereal")', popover=env.t('etherealLion'), popover-trigger='mouseenter', popover-placement='bottom')
//.Mount_Head_LionCub-Ethereal
script(type='text/ng-template', id='partials/options.inventory.pets.html')
.stable
table(style='width:100%')
tr
td(style='width:144px')
.npc_matt
td
.popover.static-popover.fade.right.in(style='max-width: 550px; margin-left: 10px;')
.arrow
h3.popover-title
a(target='_blank', href='http://www.kickstarter.com/profile/mattboch')=env.t('mattBoch')
.popover-content
p
=env.t('mattBochText1') + ' '
| <a target='_blank' href='https://f.cloud.github.com/assets/2374703/164631/3ed5fa6c-78cd-11e2-8743-f65ac477b55e.png'>
=env.t('mattBochText2')
| </a>
= ' ' + env.t('mattBochText3')
h4= '{{petCount}} / {{totalPets}} ' + env.t('petsFound')
.container-fluid
.stable.row
.col-md-2
.npc_matt
.col-md-10
.popover.static-popover.fade.right.in
.arrow
h3.popover-title
a(target='_blank', href='http://www.kickstarter.com/profile/mattboch')=env.t('mattBoch')
.popover-content
p
=env.t('mattBochText1') + ' '
| <a target='_blank' href='https://f.cloud.github.com/assets/2374703/164631/3ed5fa6c-78cd-11e2-8743-f65ac477b55e.png'>
=env.t('mattBochText2')
| </a>
= ' ' + env.t('mattBochText3')
h4= env.t('beastmasterProgress') + ': {{petCount}} / {{totalPets}} ' + env.t('petsFound')
menu.pets(type='list')
li.customize-menu(ng-repeat='egg in Content.eggs')
.col-md-12
menu.pets(type='list')
li.customize-menu(ng-repeat='egg in Content.dropEggs')
menu
div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.text}}', popover-placement='bottom', ng-init='pet = egg.key+"-"+potion.key')
button(class="pet-button Pet-{{pet}}", ng-if='user.items.pets[pet]>0', ng-class='{active: user.items.currentPet == pet, selectableInventory: selectedFood}', ng-click='choosePet(egg.key, potion.key)')
.progress(ng-show='!user.items.mounts[pet]')
.progress-bar.progress-bar-success(style="width:{{user.items.pets[pet]/.5}}%")
button(class="pet-button pet-not-owned", ng-if='!user.items.pets[pet]')
.PixelPaw
// How to apply this style in stylus files?
button(class="pet-button Pet-{{pet}}" ng-if='user.items.pets[pet]<0',style='opacity:0.1;filter:alpha(opacity=10)')
//if this could be changed to the opacity talked about? can't figure it out
.col-md-12
h4=env.t('questPets')
menu.pets(type='list')
li.customize-menu(ng-repeat='egg in Content.questEggs')
menu
div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.text}}', popover-placement='bottom', ng-init='pet = egg.key+"-"+potion.key')
button(class="pet-button Pet-{{pet}}", ng-if='user.items.pets[pet]>0', ng-class='{active: user.items.currentPet == pet, selectableInventory: selectedFood}', ng-click='choosePet(egg.key, potion.key)')
.progress(ng-show='!user.items.mounts[pet]')
.progress-bar.progress-bar-success(style="width:{{user.items.pets[pet]/.5}}%")
button(class="pet-button pet-not-owned", ng-if='!user.items.pets[pet]')
.PixelPaw
button(class="pet-button Pet-{{pet}}" ng-if='user.items.pets[pet]<0',style='opacity:0.1;filter:alpha(opacity=10)')
.col-md-12
h4=env.t('rarePets')
menu
div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.text}}', popover-placement='bottom', ng-init='pet = egg.key+"-"+potion.key')
button(class="pet-button Pet-{{pet}}", ng-if='user.items.pets[pet]>0', ng-class='{active: user.items.currentPet == pet, selectableInventory: selectedFood}', ng-click='choosePet(egg.key, potion.key)')
.progress(ng-class='{"progress-success": user.items.pets[pet]<50}')
.bar(style="width: {{user.items.pets[pet]/.5}}%;")
button(class="pet-button pet-not-owned", ng-if='!user.items.pets[pet]')
.PixelPaw
button(class="pet-button Pet-{{pet}}" ng-if='user.items.pets[pet]<0',style='opacity:0.1;filter:alpha(opacity=10)')
//if this could be changed to the opacity talked about? can't figure it out
div
button(ng-if='user.items.pets["Wolf-Veteran"]', class="pet-button Pet-Wolf-Veteran", ng-class='{active: user.items.currentPet == "Wolf-Veteran"}', ng-click='choosePet("Wolf", "Veteran")', popover=env.t('veteranWolf'), popover-trigger='mouseenter', popover-placement='bottom')
button(ng-if='user.items.pets["Wolf-Cerberus"]', class="pet-button Pet-Wolf-Cerberus", ng-class='{active: user.items.currentPet == "Wolf-Cerberus"}', ng-click='choosePet("Wolf", "Cerberus")', popover=env.t('cerberusPup'), popover-trigger='mouseenter', popover-placement='bottom')
button(ng-if='user.items.pets["Turkey-Base"]', class="pet-button Pet-Turkey-Base", ng-class='{active: user.items.currentPet == "Turkey-Base"}', ng-click='choosePet("Turkey", "Base")', popover=env.t('turkey'), popover-trigger='mouseenter', popover-placement='bottom')
button(ng-if='user.items.pets["BearCub-Polar"]', class="pet-button Pet-BearCub-Polar", ng-class='{active: user.items.currentPet == "BearCub-Polar"}', ng-click='choosePet("BearCub", "Polar")', popover=env.t('polarBearPup'), popover-trigger='mouseenter', popover-placement='bottom')
button(ng-if='user.items.pets["Dragon-Hydra"]', class="pet-button Pet-Dragon-Hydra", ng-class='{active: user.items.currentPet == "Dragon-Hydra"}', ng-click='choosePet("Dragon", "Hydra")', popover=env.t('hydra'), popover-trigger='mouseenter', popover-placement='bottom')
a(target='_blank', href='http://habitrpg.wikia.com/wiki/Contributing_to_HabitRPG')
button(ng-if='!user.items.pets["Dragon-Hydra"]', class="pet-button pet-not-owned", popover-trigger='mouseenter', popover-placement='right', popover=env.t('rarePetPop1'), popover-title=env.t('rarePetPop2'))
.PixelPaw-Gold
h4=env.t('rarePets')
menu
div
button(ng-if='user.items.pets["Wolf-Veteran"]', class="pet-button Pet-Wolf-Veteran", ng-class='{active: user.items.currentPet == "Wolf-Veteran"}', ng-click='choosePet("Wolf", "Veteran")', popover=env.t('veteranWolf'), popover-trigger='mouseenter', popover-placement='bottom')
button(ng-if='user.items.pets["Wolf-Cerberus"]', class="pet-button Pet-Wolf-Cerberus", ng-class='{active: user.items.currentPet == "Wolf-Cerberus"}', ng-click='choosePet("Wolf", "Cerberus")', popover=env.t('cerberusPup'), popover-trigger='mouseenter', popover-placement='bottom')
button(ng-if='user.items.pets["Turkey-Base"]', class="pet-button Pet-Turkey-Base", ng-class='{active: user.items.currentPet == "Turkey-Base"}', ng-click='choosePet("Turkey", "Base")', popover=env.t('turkey'), popover-trigger='mouseenter', popover-placement='bottom')
button(ng-if='user.items.pets["BearCub-Polar"]', class="pet-button Pet-BearCub-Polar", ng-class='{active: user.items.currentPet == "BearCub-Polar"}', ng-click='choosePet("BearCub", "Polar")', popover=env.t('polarBearPup'), popover-trigger='mouseenter', popover-placement='bottom')
button(ng-if='user.items.pets["Dragon-Hydra"]', class="pet-button Pet-Dragon-Hydra", ng-class='{active: user.items.currentPet == "Dragon-Hydra"}', ng-click='choosePet("Dragon", "Hydra")', popover=env.t('hydra'), popover-trigger='mouseenter', popover-placement='bottom')
a(target='_blank', href='http://habitrpg.wikia.com/wiki/Contributing_to_HabitRPG')
button(ng-if='!user.items.pets["Dragon-Hydra"]', class="pet-button pet-not-owned", popover-trigger='mouseenter', popover-placement='right', popover=env.t('rarePetPop1'), popover-title=env.t('rarePetPop2'))
.PixelPaw-Gold
.well.food-tray
p(ng-show='foodCount < 1')=env.t('noFood')
menu.inventory-list(type='list', ng-if='foodCount > 0')
li.customize-menu
menu.pets-menu(label=env.t('food'))
div(ng-repeat='(food,points) in ownedItems(user.items.food)')
button.customize-option(popover-append-to-body='true', popover='{{Content.food[food].notes}}', popover-title='{{Content.food[food].text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='chooseFood(food)', class='Pet_Food_{{food}}')
.badge.badge-info.stack-count {{points}}
// Remove this once we have images in
p {{Content.food[food].text}}
.well.food-tray
p(ng-show='foodCount < 1')=env.t('noFood')
menu.inventory-list(type='list', ng-if='foodCount > 0')
li.customize-menu
menu.pets-menu(label=env.t('food'))
div(ng-repeat='(food,points) in ownedItems(user.items.food)')
button.customize-option(popover-append-to-body='true', popover='{{Content.food[food].notes}}', popover-title='{{Content.food[food].text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='chooseFood(food)', class='Pet_Food_{{food}}')
.badge.badge-info.stack-count {{points}}
// Remove this once we have images in
p {{Content.food[food].text}}
+129 -121
View File
@@ -1,15 +1,15 @@
script(id='partials/options.profile.avatar.html', type='text/ng-template')
.row-fluid
.span4
.row
.col-md-4
h3=env.t('bodyBody')
small
| 2 <span class="Pet_Currency_Gem1x inline-gems"/> /
| 2 <span class="Pet_Currency_Gem1x inline-gems"></span> /
= ' ' + env.t('locked')
h5=env.t('bodySize')
.btn-group
button.btn.btn-small(ng-class='{active: user.preferences.size=="slim"}', ng-click='set({"preferences.size":"slim"})')=env.t('bodySlim')
button.btn.btn-small(ng-class='{active: user.preferences.size=="broad"}', ng-click='set({"preferences.size":"broad"})')=env.t('bodyBroad')
button.btn.btn-sm.btn-default(ng-class='{active: user.preferences.size=="slim"}', ng-click='set({"preferences.size":"slim"})')=env.t('bodySlim')
button.btn.btn-sm.btn-default(ng-class='{active: user.preferences.size=="broad"}', ng-click='set({"preferences.size":"broad"})')=env.t('bodyBroad')
menu(type='list')
li.customize-menu
@@ -21,31 +21,30 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
each shirt in ['convict', 'cross', 'fire', 'horizon', 'ocean', 'purple', 'rainbow', 'redblue', 'thunder', 'tropical', 'zombie']
button.customize-option(type='button', class='{{user.preferences.size}}_shirt_'+shirt, ng-class='{locked: !user.purchased.shirt.'+shirt+'}', ng-click='unlock("shirt.'+shirt+'")')
menu
button.btn.btn-small.btn-primary(ng-hide="user.purchased.shirt.convict && user.purchased.shirt.cross && user.purchased.shirt.fire && user.purchased.shirt.horizon && user.purchased.shirt.ocean && user.purchased.shirt.purple && user.purchased.shirt.rainbow && user.purchased.shirt.redblue && user.purchased.shirt.thunder && user.purchased.shirt.tropical && user.purchased.shirt.zombie", ng-click='unlock("shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
button.btn.btn-sm.btn-primary(ng-hide="user.purchased.shirt.convict && user.purchased.shirt.cross && user.purchased.shirt.fire && user.purchased.shirt.horizon && user.purchased.shirt.ocean && user.purchased.shirt.purple && user.purchased.shirt.rainbow && user.purchased.shirt.redblue && user.purchased.shirt.thunder && user.purchased.shirt.tropical && user.purchased.shirt.zombie", ng-click='unlock("shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
.span4
.col-md-4
h3=env.t('bodyHead')
small
| 2 <span class="Pet_Currency_Gem1x inline-gems"/> /
| 2 <span class="Pet_Currency_Gem1x inline-gems"></span> /
= ' ' + env.t('locked')
menu(type='list')
// For special events code, see commit dfa27b3
// Color
li.customize-menu
menu(label=env.t('color'))
each v,k in {'c8c8c8':'white','903a00':'brown','cfb853':'blond','ec720f':'red','2e2e2e':'black'}
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:##{k};', ng-click='set({"preferences.hair.color": "#{v}"})')
// Special Events
li.customize-menu.well.limited-edition
.label.label-info.pull-right(popover=env.t('limited31Jan'), popover-title=env.t('limitedEdition'), popover-placement='right', popover-trigger='mouseenter')
=env.t('limitedEdition')
|
i.icon.icon-question-sign
menu(label=env.t('winterColors'))
button(type='button', class='customize-option hair', style='background-color:##{k};', ng-click='set({"preferences.hair.color": "#{v}"})')
each color in ['candycane','frost','winternight','holly']
button(type='button', ng-class='{locked: !user.purchased.hair.color.#{color}}', class='customize-option hair_bangs_1_#{color}', style='width: 40px; height: 40px;', ng-click='unlock("hair.color.#{color}")')
button.btn.btn-small.btn-primary(ng-hide='user.purchased.hair.color.candycane && user.purchased.hair.color.frost && user.purchased.hair.color.winternight && user.purchased.hair.color.holly', ng-click='unlock("hair.color.candycane,hair.color.frost,hair.color.winternight,hair.color.holly")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
button(type='button', ng-if='user.purchased.hair.color.#{color}', class='customize-option hair hair_bangs_1_#{color}', style='width: 40px; height: 40px;', ng-click='unlock("hair.color.#{color}")')
li.customize-menu
menu(label=env.t('rainbowColors'))
each color in ['rainbow','yellow','green','purple','blue','TRUred']
button(type='button', ng-class='{locked: !user.purchased.hair.color.#{color}}', class='customize-option hair hair_bangs_1_#{color}', ng-click='unlock("hair.color.#{color}")')
button.btn.btn-sm.btn-primary(ng-hide='user.purchased.hair.color.rainbow && user.purchased.hair.color.yellow && user.purchased.hair.color.green && user.purchased.hair.color.purple && user.purchased.hair.color.blue && user.purchased.hair.color.TRUred', ng-click='unlock("hair.color.rainbow,hair.color.yellow,hair.color.green,hair.color.purple,hair.color.blue,hair.color.TRUred")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
h5=env.t('bodyHair')
// Bangs
@@ -64,7 +63,7 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
when true: button(class='hair_base_#{k}_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.base":#{k}})')
when false: button(class='hair_base_#{k}_{{user.preferences.hair.color}} customize-option', type='button', ng-class='{locked: !user.purchased.hair.base.#{k}}', ng-click='unlock("hair.base.#{k}")')
button.btn.btn-small.btn-primary(ng-hide='user.purchased.hair.base.2 && user.purchased.hair.base.4 && user.purchased.hair.base.5 && user.purchased.hair.base.6 && user.purchased.hair.base.7 && user.purchased.hair.base.8', ng-click='unlock("hair.base.2,hair.base.4,hair.base.5,hair.base.6,hair.base.7,hair.base.8")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
button.btn.btn-sm.btn-primary(ng-hide='user.purchased.hair.base.2 && user.purchased.hair.base.4 && user.purchased.hair.base.5 && user.purchased.hair.base.6 && user.purchased.hair.base.7 && user.purchased.hair.base.8', ng-click='unlock("hair.base.2,hair.base.4,hair.base.5,hair.base.6,hair.base.7,hair.base.8")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
h5=env.t('bodyFacialHair')
@@ -82,12 +81,12 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
each num in [1,2]
button(class='hair_mustache_#{num}_{{user.preferences.hair.color}} customize-option', type='button', ng-class='{locked: !user.purchased.hair.mustache.#{num}}', ng-click='unlock("hair.mustache.#{num}")')
button.btn.btn-small.btn-primary(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('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
button.btn.btn-sm.btn-primary(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('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
.span4
.col-md-4
h3=env.t('bodySkin')
small
| 2 <span class="Pet_Currency_Gem1x inline-gems"/> /
| 2 <span class="Pet_Currency_Gem1x inline-gems"></span> /
= ' ' + env.t('locked')
// skin
li.customize-menu
@@ -101,7 +100,7 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
menu
each color in ['eb052b','f69922','f5d70f','0ff591','2b43f6','d7a9f7','800ed0','rainbow']
button.customize-option(type='button', class='skin_#{color}', ng-class='{locked: !user.purchased.skin.#{color}}', ng-click='unlock("skin.#{color}")')
button.btn.btn-small.btn-primary(ng-hide='user.purchased.skin.eb052b && user.purchased.skin.f69922 && user.purchased.skin.f5d70f && user.purchased.skin.0ff591 && user.purchased.skin.2b43f6 && user.purchased.skin.d7a9f7 && user.purchased.skin.800ed0 && user.purchased.skin.rainbow', ng-click='unlock("skin.eb052b,skin.f69922,skin.f5d70f,skin.0ff591,skin.2b43f6,skin.d7a9f7,skin.800ed0,skin.rainbow")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
button.btn.btn-sm.btn-primary(ng-hide='user.purchased.skin.eb052b && user.purchased.skin.f69922 && user.purchased.skin.f5d70f && user.purchased.skin.0ff591 && user.purchased.skin.2b43f6 && user.purchased.skin.d7a9f7 && user.purchased.skin.800ed0 && user.purchased.skin.rainbow', ng-click='unlock("skin.eb052b,skin.f69922,skin.f5d70f,skin.0ff591,skin.2b43f6,skin.d7a9f7,skin.800ed0,skin.rainbow")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
// Special Events
// restore to d4df481 to see purchasing + "limited edition" code
@@ -112,108 +111,117 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
button.customize-option(type='button', class='skin_#{color}', ng-if='user.purchased.skin.#{color}', ng-click='unlock("skin.#{color}")')
script(id='partials/options.profile.stats.html', type='text/ng-template')
.row-fluid
.border-right(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "span4" : "span6"')
include ../shared/profiles/stats
.span4.border-right.allocate-stats(ng-if='user.flags.classSelected && !user.preferences.disableClasses')
h3=env.t('characterBuild')
h4
=env.t('class') + ': '
span {{ {warrior:'Warrior',wizard:'Mage',healer:'Healer',rogue:'Rogue'}[user.stats.class] }}&nbsp;
a.btn.btn-danger.btn-mini(ng-click='changeClass(null)')=env.t('changeClass')
small 3 <span class="Pet_Currency_Gem1x inline-gems"/>
table.table.table-striped
tr
td
strong
{{user.stats.points}}&nbsp;
=env.t('unallocated')
&nbsp;
i.icon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('levelPopover'))
td
tr
td(colspan=2)
fieldset.auto-allocate
label.checkbox
input(type='checkbox', ng-model='user.preferences.automaticAllocation', ng-change='set({"preferences.automaticAllocation": user.preferences.automaticAllocation?true: false})', ng-click='set({"preferences.allocationMode":"taskbased"})')
=env.t('autoAllocation')
&nbsp;
i.icon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('autoAllocationPop'))
form(ng-show='user.preferences.automaticAllocation',style='margin-left:1em')
label.radio
input(type='radio', name='allocationMode', value='flat', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "flat"})')
=env.t('evenAllocation')
&nbsp;
i.icon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('evenAllocationPop'))
label.radio
input(type='radio', name='allocationMode', value='classbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "classbased"})')
=env.t('classAllocation')
&nbsp;
i.icon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('classAllocationPop'))
label.radio
input(type='radio', name='allocationMode', value='taskbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "taskbased"})')
=env.t('taskAllocation')
&nbsp;
i.icon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('taskAllocationPop'))
div(ng-show='user.preferences.automaticAllocation && !(user.preferences.allocationMode === "taskbased") && (user.stats.points > 0)')
a.btn.btn-primary.btn-mini(ng-click='user.ops.allocateNow({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('distributePointsPop'))
i.icon-download.icon-white
&nbsp;
=env.t('distributePoints')
tr
td= env.t('allocateStr') + ' {{user.stats.str}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("str")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateStrPop')) +
tr
td= env.t('allocateCon') + ' {{user.stats.con}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("con")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateConPop')) +
tr
td= env.t('allocatePer') + ' {{user.stats.per}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("per")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocatePerPop')) +
tr
td= env.t('allocateInt') + ' {{user.stats.int}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("int")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateIntPop')) +
div(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "span4" : "span6"')
include ../shared/profiles/achievements
.container-fluid
.row
.border-right(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "col-md-4" : "col-md-6"')
include ../shared/profiles/stats
.col-md-4.border-right.allocate-stats(ng-if='user.flags.classSelected && !user.preferences.disableClasses')
h3=env.t('characterBuild')
h4
=env.t('class') + ': '
span {{ {warrior:'Warrior',wizard:'Mage',healer:'Healer',rogue:'Rogue'}[user.stats.class] }}&nbsp;
a.btn.btn-danger.btn-xs(ng-click='changeClass(null)')=env.t('changeClass')
small 3 <span class="Pet_Currency_Gem1x inline-gems"/>
table.table.table-striped
tr
td
strong
{{user.stats.points}}&nbsp;
=env.t('unallocated')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('levelPopover'))
td
tr
td(colspan=2)
fieldset.auto-allocate
.checkbox
label
input(type='checkbox', ng-model='user.preferences.automaticAllocation', ng-change='set({"preferences.automaticAllocation": user.preferences.automaticAllocation?true: false})', ng-click='set({"preferences.allocationMode":"taskbased"})')
=env.t('autoAllocation')
|&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('autoAllocationPop'))
form(ng-show='user.preferences.automaticAllocation',style='margin-left:1em')
.radio
label
input(type='radio', name='allocationMode', value='flat', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "flat"})')
=env.t('evenAllocation')
|&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('evenAllocationPop'))
.radio
label
input(type='radio', name='allocationMode', value='classbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "classbased"})')
=env.t('classAllocation')
|&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('classAllocationPop'))
.radio
label
input(type='radio', name='allocationMode', value='taskbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "taskbased"})')
=env.t('taskAllocation')
|&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('taskAllocationPop'))
div(ng-show='user.preferences.automaticAllocation && !(user.preferences.allocationMode === "taskbased") && (user.stats.points > 0)')
a.btn.btn-primary.btn-xs(ng-click='user.ops.allocateNow({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('distributePointsPop'))
span.glyphicon.glyphicon-download
&nbsp;
=env.t('distributePoints')
tr
td= env.t('allocateCon') + ' {{user.stats.con}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("con")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateConPop')) +
tr
td= env.t('allocateInt') + ' {{user.stats.int}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("int")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateIntPop')) +
tr
td= env.t('allocatePer') + ' {{user.stats.per}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("per")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocatePerPop')) +
tr
td= env.t('allocateStr') + ' {{user.stats.str}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("str")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateStrPop')) +
div(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "col-md-4" : "col-md-6"')
include ../shared/profiles/achievements
script(id='partials/options.profile.profile.html', type='text/ng-template')
button.btn.btn-default(ng-click='_editing.profile = true', ng-show='!_editing.profile')= env.t('edit')
button.btn.btn-primary(ng-click='save()', ng-show='_editing.profile')= env.t('save')
div(ng-show='!_editing.profile')
h4=env.t('displayName')
span(ng-show='profile.profile.name') {{profile.profile.name}}
span.muted(ng-hide='profile.profile.name') -&nbsp;
=env.t('none')
| &nbsp;-
.container-fluid
.row
.col-md-12(ng-show='!_editing.profile')
button.btn.btn-default(ng-click='_editing.profile = true', ng-show='!_editing.profile')= env.t('edit')
h4=env.t('displayName')
span(ng-show='profile.profile.name') {{profile.profile.name}}
span.muted(ng-hide='profile.profile.name') -&nbsp;
=env.t('none')
| &nbsp;-
h4=env.t('displayPhoto')
img(ng-show='profile.profile.imageUrl', ng-src='{{profile.profile.imageUrl}}')
span.muted(ng-hide='profile.profile.imageUrl') -&nbsp;
=env.t('none')
| &nbsp;-
h4=env.t('displayPhoto')
img(ng-show='profile.profile.imageUrl', ng-src='{{profile.profile.imageUrl}}')
span.muted(ng-hide='profile.profile.imageUrl') -&nbsp;
=env.t('none')
| &nbsp;-
h4=env.t('displayBlurb')
markdown(ng-show='profile.profile.blurb', ng-model='profile.profile.blurb')
span.muted(ng-hide='profile.profile.blurb') -&nbsp;
=env.t('none')
| &nbsp;-
//{{profile.profile.blurb | linky:'_blank'}}
h4=env.t('displayBlurb')
markdown(ng-show='profile.profile.blurb', ng-model='profile.profile.blurb')
span.muted(ng-hide='profile.profile.blurb') -&nbsp;
=env.t('none')
| &nbsp;-
//{{profile.profile.blurb | linky:'_blank'}}
div.whatever-options(ng-show='_editing.profile')
// TODO use photo-upload instead: https://groups.google.com/forum/?fromgroups=#!topic/derbyjs/xMmADvxBOak
.control-group.option-large
label.control-label=env.t('displayName')
input.option-content(type='text', placeholder=env.t('fullName'), ng-model='editingProfile.name')
.control-group.option-large
label.control-label=env.t('photoUrl')
input.option-content(type='url', ng-model='editingProfile.imageUrl', placeholder=env.t('imageUrl'))
.control-group.option-large
label.control-label=env.t('displayBlurb')
textarea.option-content(style='height:15em;', placeholder=env.t('displayBlurb'), ng-model='editingProfile.blurb')
include ../shared/formatting-help
form.col-md-4(ng-show='_editing.profile', ng-submit='save()')
input.btn.btn-primary(type='submit', value=env.t('save'))
// TODO use photo-upload instead: https://groups.google.com/forum/?fromgroups=#!topic/derbyjs/xMmADvxBOak
.form-group
label=env.t('displayName')
input.form-control(type='text', placeholder=env.t('fullName'), ng-model='editingProfile.name')
.form-group
label=env.t('photoUrl')
input.form-control(type='url', ng-model='editingProfile.imageUrl', placeholder=env.t('imageUrl'))
.form-group
label=env.t('displayBlurb')
textarea.form-control(rows=5, placeholder=env.t('displayBlurb'), ng-model='editingProfile.blurb')
include ../shared/formatting-help
script(id='partials/options.profile.html', type="text/ng-template")
ul.nav.nav-tabs
+101 -93
View File
@@ -17,102 +17,108 @@ script(id='partials/options.settings.html', type="text/ng-template")
div(ui-view)
script(type='text/ng-template', id='partials/options.settings.settings.html')
.row-fluid
.personal-options.span6
h2=env.t('settings')
h4=env.t('customDayStart')
.option-group.option-short
input.option-content.option-time(type='number', min='0', max='24', ng-model='user.preferences.dayStart', ng-change='saveDayStart()')
span.input-suffix= ':00 (' + env.t('24HrClock') + ')'
div
small
div=env.t('clockInfo')
.alert.alert-danger
=env.t('subWarning1')
a(href='https://github.com/HabitRPG/habitrpg/issues/1057' target='_blank')=env.t('subWarning2')
=env.t('subWarning3')
hr
h4=env.t('language')
select(ng-model='language.code', ng-options='lang.code as lang.name for lang in avalaibleLanguages', ng-change='changeLanguage()')
hr
h4=env.t('misc')
label.checkbox
input(type='checkbox', ng-click='set({"preferences.hideHeader":user.preferences.hideHeader?false:true})', ng-checked='user.preferences.hideHeader!==true')
=env.t('showHeader')
&nbsp;
i.icon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('showHeaderPop'))
label.checkbox
input(type='checkbox', ng-click='toggleStickyHeader()', ng-checked='user.preferences.stickyHeader!==false')
=env.t('stickyHeader')
&nbsp;
i.icon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('stickyHeaderPop'))
label.checkbox
input(type='checkbox', ng-model='user.preferences.newTaskEdit', ng-change='set({"preferences.newTaskEdit": user.preferences.newTaskEdit?true: false})')
=env.t('newTaskEdit')
&nbsp;
i.icon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('newTaskEditPop'))
label.checkbox
input(type='checkbox', ng-model='user.preferences.tagsCollapsed', ng-change='set({"preferences.tagsCollapsed": user.preferences.tagsCollapsed?true: false})')
=env.t('startCollapsed')
&nbsp;
i.icon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('startCollapsedPop'))
label.checkbox
input(type='checkbox', ng-model='user.preferences.advancedCollapsed', ng-change='set({"preferences.advancedCollapsed": user.preferences.advancedCollapsed?true: false})')
=env.t('startAdvCollapsed')
&nbsp;
i.icon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('startAdvCollapsedPop'))
button.btn(ng-click='showTour()', popover-placement='right', popover-trigger='mouseenter', popover=env.t('restartTour'))= env.t('showTour')
br
button.btn(ng-click='showBailey()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('showBaileyPop'))= env.t('showBailey')
br
button.btn(ng-click='modals.restore = true', popover-trigger='mouseenter', popover-placement='right', popover=env.t('fixValPop'))= env.t('fixVal')
div(ng-if='user.preferences.disableClasses==true')
button.btn(ng-click='user.ops.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass')
div(ng-if='!user.preferences.disableClasses && user.flags.classSelected')
button.btn(ng-click='showClassesTour()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('classTourPop'))= env.t('showClass')
//- Why is ng-if='user.auth.local' validating for users *without* user.auth.local (facebook users)? adding .username here for extra
div(ng-if='user.auth.local.username')
.containter-fluid
.row
.personal-options.col-md-6
h2=env.t('settings')
h4=env.t('customDayStart')
.form-group
input.form-control(type='number', min='0', max='24', ng-model='user.preferences.dayStart', ng-change='saveDayStart()')
span.input-suffix= ':00 (' + env.t('24HrClock') + ')'
div
small
div=env.t('clockInfo')
.alert.alert-danger.alert-sm
=env.t('subWarning1')
|&nbsp;
a(href='https://github.com/HabitRPG/habitrpg/issues/1057' target='_blank')=env.t('subWarning2')
|&nbsp;
=env.t('subWarning3')
hr
h4=env.t('changePass')
form(ng-submit='changePassword(changePass)', ng-show='user.auth.local')
.control-group
input(type='password', placeholder=env.t('oldPass'), ng-model='changePass.oldPassword', required)
.control-group
input(type='password', placeholder=env.t('newPass'), ng-model='changePass.newPassword', required)
.control-group
input(type='password', placeholder=env.t('confirmPass'), ng-model='changePass.confirmNewPassword', required)
input.btn(type='submit', value=env.t('submit'))
h4=env.t('language')
select(ng-model='language.code', ng-options='lang.code as lang.name for lang in avalaibleLanguages', ng-change='changeLanguage()')
hr
h4=env.t('misc')
.checkbox
label
input(type='checkbox', ng-click='set({"preferences.hideHeader":user.preferences.hideHeader?false:true})', ng-checked='user.preferences.hideHeader!==true')
=env.t('showHeader')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('showHeaderPop'))
.checkbox
label
input(type='checkbox', ng-click='toggleStickyHeader()', ng-checked='user.preferences.stickyHeader!==false')
=env.t('stickyHeader')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('stickyHeaderPop'))
.checkbox
label
input(type='checkbox', ng-model='user.preferences.newTaskEdit', ng-change='set({"preferences.newTaskEdit": user.preferences.newTaskEdit?true: false})')
=env.t('newTaskEdit')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('newTaskEditPop'))
.checkbox
label
input(type='checkbox', ng-model='user.preferences.tagsCollapsed', ng-change='set({"preferences.tagsCollapsed": user.preferences.tagsCollapsed?true: false})')
=env.t('startCollapsed')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('startCollapsedPop'))
.checkbox
label
input(type='checkbox', ng-model='user.preferences.advancedCollapsed', ng-change='set({"preferences.advancedCollapsed": user.preferences.advancedCollapsed?true: false})')
=env.t('startAdvCollapsed')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('startAdvCollapsedPop'))
button.btn.btn-default(ng-click='showTour()', popover-placement='right', popover-trigger='mouseenter', popover=env.t('restartTour'))= env.t('showTour')
button.btn.btn-default(ng-click='showBailey()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('showBaileyPop'))= env.t('showBailey')
button.btn.btn-default(ng-click='openRestoreModal()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('fixValPop'))= env.t('fixVal')
button.btn.btn-default(ng-if='user.preferences.disableClasses==true', ng-click='user.ops.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass')
button.btn.btn-default(ng-if='!user.preferences.disableClasses && user.flags.classSelected', ng-click='showClassesTour()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('classTourPop'))= env.t('showClass')
hr
h4=env.t('dangerZone')
a.btn.btn-danger(ng-click='modals.reset = true', popover-trigger='mouseenter', popover-placement='right', popover=env.t('resetAccPop'))= env.t('resetAccount')
a.btn.btn-danger(ng-click='modals.delete = true', popover-trigger='mouseenter', popover=env.t('deleteAccPop'))= env.t('deleteAccount')
//- Why is ng-if='user.auth.local' validating for users *without* user.auth.local (facebook users)? adding .username here for extra
div(ng-if='user.auth.local.username')
hr
h4=env.t('changePass')
form(ng-submit='changePassword(changePass)', ng-show='user.auth.local')
.form-group
input.form-control(type='password', placeholder=env.t('oldPass'), ng-model='changePass.oldPassword', required)
.form-group
input.form-control(type='password', placeholder=env.t('newPass'), ng-model='changePass.newPassword', required)
.form-group
input.form-control(type='password', placeholder=env.t('confirmPass'), ng-model='changePass.confirmNewPassword', required)
input.btn.btn-default(type='submit', value=env.t('submit'))
hr
h4=env.t('dangerZone')
a.btn.btn-danger(ng-click='openModal("reset", {controller:"SettingsCtrl"})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('resetAccPop'))= env.t('resetAccount')
a.btn.btn-danger(ng-click='openModal("delete", {controller:"SettingsCtrl"})', popover-trigger='mouseenter', popover=env.t('deleteAccPop'))= env.t('deleteAccount')
script(type='text/ng-template', id='partials/options.settings.api.html')
.row.fluid
.span6
h2=env.t('API')
small=env.t('APIText')
h6=env.t('userId')
pre.prettyprint {{user.id}}
h6=env.t('APIToken')
pre.prettyprint {{user.apiToken}}
h6=env.t('qrCode')
img(src='https://chart.googleapis.com/chart?cht=qr&chs=200x200&chl=%7B%22address%22%3A%22https%3A%2F%2Fhabitrpg.com%22%2C%22user%22%3A%22{{user.id}}%22%2C%22key%22%3A%22{{user.apiToken}}%22%7D&choe=UTF-8&chld=L', alt='qrcode')
.containter-fluid
.row
.col-md-6
h2=env.t('API')
small=env.t('APIText')
h6=env.t('userId')
pre.prettyprint {{user.id}}
h6=env.t('APIToken')
pre.prettyprint {{user.apiToken}}
h6=env.t('qrCode')
img(src='https://chart.googleapis.com/chart?cht=qr&chs=200x200&chl=%7B%22address%22%3A%22https%3A%2F%2Fhabitrpg.com%22%2C%22user%22%3A%22{{user.id}}%22%2C%22key%22%3A%22{{user.apiToken}}%22%7D&choe=UTF-8&chld=L', alt='qrcode')
script(id='partials/options.settings.export.html', type="text/ng-template")
.row.fluid
.span6
h2=env.t('dataExport')
small=env.t('saveData')
h4=env.t('habitHistory')
=env.t('exportHistory')
a(href="/export/history.csv")= ' ' + env.t('csv')
h4=env.t('userData')
=env.t('exportUserData')
a(href="/export/userdata.xml")= ' ' + env.t('xml') + ' '
a(href="/export/userdata.json")= env.t('json')
.containter-fluid
.row
.col-md-6
h2=env.t('dataExport')
small=env.t('saveData')
h4=env.t('habitHistory')
=env.t('exportHistory')
a(href="/export/history.csv")= ' ' + env.t('csv')
h4=env.t('userData')
=env.t('exportUserData')
a(href="/export/userdata.xml")= ' ' + env.t('xml') + ' '
a(href="/export/userdata.json")= env.t('json')
script(id='partials/options.settings.subscription.perks.html',type='text/ng-template')
table.table.table-striped
@@ -153,7 +159,9 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template')
div(ng-if='user.purchased.plan.customerId')
.well
div(style='font-size:22px')
i.icon-ok=env.t('subscribed')
p.lead
=env.t('subscribed')
| &nbsp;
span.glyphicon.glyphicon-ok
div(ng-include="'partials/options.settings.subscription.perks.html'")
.btn.btn-small.btn-danger(ng-click='cancelSubscription()')=env.t('cancelSub')
.btn.btn-sm.btn-danger(ng-click='cancelSubscription()')=env.t('cancelSub')
+13 -8
View File
@@ -1,18 +1,23 @@
// ------ Challenges -------
.modal(style='position: relative;top: auto;left: auto;right: auto;margin: 0 auto 20px;z-index: 1;max-width: 100%;')
.modal-header
h3=env.t('challenges')
.panel.panel-default
.panel-heading
h3.panel-title=env.t('challenges')
a.pull-right(target='_blank', href='https://trello.com/card/challenges-individual-party-guild-public/50e5d3684fe3a7266b0036d6/58')
i.icon-question-sign
.modal-body(bindonce='group.challenges')
span.glyphicon.glyphicon-question-sign
.panel-body.modal-fixed-height(bindonce='group.challenges')
div(ng-if='group.challenges.length > 0')
table.table.table-striped
tr(ng-repeat='challenge in group.challenges')
td
a(ui-sref='options.social.challenges.detail({cid:challenge._id})') {{challenge.name}}
div(ng-if='group.challenges.length == 0')
p=env.t('noChallenges')
a(class='label' ui-sref='options.social.challenges')
i.icon-bullhorn
p
|&nbsp;
=env.t('noChallenges')
|&nbsp;
a.label.label-primary(ui-sref='options.social.challenges')
span.glyphicon.glyphicon-bullhorn
|&nbsp;
=env.t('challenges')
|&nbsp;
=env.t('toCreate')
+118 -108
View File
@@ -1,5 +1,5 @@
script(type='text/ng-template', id='partials/options.social.challenges.detail.close.html')
a.btn.btn-small.btn-danger(ng-click="delete(closingChal)")=env.t('delete')
a.btn.btn-sm.btn-danger(ng-click="delete(closingChal)")=env.t('delete')
h5= '- ' + env.t('or') + ' -'
select(ui-select2, ng-required=true, ng-model='closingChal.winner', data-placeholder=env.t('selectWinner'), ng-change='selectWinner(closingChal)', )
option(value='')
@@ -9,42 +9,44 @@ script(type='text/ng-template', id='partials/options.social.challenges.detail.cl
a(ng-click='cancelClosing(closingChal)')=env.t('cancel')
script(type='text/ng-template', id='partials/options.social.challenges.detail.member.html')
.modal.wide-modal
.modal-header
button.close(type='button', ng-click='$state.go("^")', aria-hidden='true') ×
h3 {{obj.profile.name}}
.modal-body
habitrpg-tasks(main=false)
.modal-footer
a.btn(ng-click='$state.go("^")')=env.t('close')
.modal.bs-modal-lg(style='display: block')
.modal-dialog.modal-lg
.modal-content
.modal-header
button.close(type='button', ng-click='$state.go("^")', aria-hidden='true') ×
h3 {{obj.profile.name}}
.modal-body
habitrpg-tasks(main=false)
.modal-footer
a.btn.btn-default(ng-click='$state.go("^")')=env.t('close')
script(type='text/ng-template', id='partials/options.social.challenges.detail.html')
// Edit button
div(bindonce='challenge', ng-if='challenge.leader==user._id')
ul.unstyled
li(ng-hide='challenge._locked==false')
button.btn.btn-small.btn-default(ng-click='challenge._locked = false')=env.t('edit')
button.btn.btn-small.btn-warning(ng-click='close(challenge, $event)', tooltip=env.t('deleteOrSelect'))=env.t('endChallenge')
li(ng-show='challenge._locked==false')
button.btn.btn-small.btn-primary(ng-click='save(challenge)')=env.t('save')
button.btn.btn-small.btn-default(ng-click='challenge._locked=true')=env.t('cancel')
div(ng-hide='challenge._locked==false')
button.btn.btn-sm.btn-default(ng-click='challenge._locked = false')=env.t('edit')
button.btn.btn-sm.btn-warning(ng-click='close(challenge, $event)', tooltip=env.t('deleteOrSelect'))=env.t('endChallenge')
div(ng-show='challenge._locked==false')
.-options
input.option-content(type='text', ng-model='challenge.name')
.option-medium
input.option-content(type='text', minlength="3", maxlength="16", ng-model='challenge.shortName', placeholder=env.t('challengeTag'), required)
textarea.option-content(cols='3', placeholder=env.t('challengeDescr'), ng-model='challenge.description')
form(ng-show='challenge._locked==false')
.form-group(ng-show='challenge._locked==false')
button.btn.btn-sm.btn-primary(ng-click='save(challenge)')=env.t('save')
button.btn.btn-sm.btn-default(ng-click='challenge._locked=true')=env.t('cancel')
.form-group
input.form-control(type='text', ng-model='challenge.name')
.form-group
input.form-control(type='text', minlength="3", maxlength="16", ng-model='challenge.shortName', placeholder=env.t('challengeTag'), required)
.form-group
textarea.form-control(cols='3', placeholder=env.t('challengeDescr'), ng-model='challenge.description')
hr
.well(bindonce='challenge', ng-if='challenge.description')
markdown(ng-model='challenge.description')
i.icon-question-sign(popover=env.t('challengeDiscription'), popover-trigger='mouseenter', popover-placement='right')
span.glyphicon.glyphicon-question-sign(popover=env.t('challengeDiscription'), popover-trigger='mouseenter', popover-placement='right')
habitrpg-tasks(obj='challenge', main=false)
// Member List
div(bindonce='challenge', ng-if='challenge.members.length > 0')
a.btn.btn-primary.btn-small.pull-right(href='/api/v2/challenges/{{challenge._id}}/csv')
a.btn.btn-primary.btn-sm.pull-right(href='/api/v2/challenges/{{challenge._id}}/csv')
=env.t('exportChallengeCSV')
h3=env.t('hows')
menu
@@ -62,94 +64,102 @@ script(type='text/ng-template', id='partials/options.social.challenges.detail.ht
div(ui-view)
script(type='text/ng-template', id='partials/options.social.challenges.html')
.row-fluid
.span2.well#challenges-filters
h3=env.t('filter') + ':'
h4=env.t('groups')
ul.unstyled
a.btn.btn-mini.pull-left(ng-click='selectAll()')=env.t('all')
a.btn.btn-mini(ng-click='selectNone()')=env.t('none')
br
label.checkbox(ng-repeat='group in groupsFilter')
input(type='checkbox', ng-model='search.group[group._id]')
| {{group.name}}
h4=env.t('membership')
label.radio
input(type='radio', name='search-participation-radio', ng-click='search._isMember = true')
=env.t('participating')
label.radio
input(type='radio', name='search-participation-radio', ng-click='search._isMember = false')
=env.t('notParticipating')
label.radio
input(type='radio', name='search-participation-radio', ng-click='search._isMember = undefined', checked='checked')
=env.t('either')
.span10
// Creation form
button.btn.btn-success#create-challenge-btn(ng-click='create()', ng-hide='newChallenge')=env.t('createChallenge')
.create-challenge-from.well(ng-if='newChallenge')
form(ng-submit='save(newChallenge)')
div
input.btn.btn-success(type='submit', value=env.t('save'))
input.btn.btn-danger(type='button', ng-click='discard()', value=env.t('discard'))
select(ng-model='newChallenge.group', ng-required='required', name='Group', ng-options='g._id as g.name for g in groups')
.challenge-options
input.option-content(type='text', ng-model='newChallenge.name', placeholder=env.t('challengeTitle'), required='required')
.container-fluid
.row
.col-md-2.well#challenges-filters
h3=env.t('filter') + ':'
h4=env.t('groups')
ul.list-unstyled
a.btn.btn-xs.btn-default.pull-left(ng-click='selectAll()')=env.t('all')
a.btn.btn-xs.btn-default(ng-click='selectNone()')=env.t('none')
br
.checkbox(ng-repeat='group in groupsFilter')
label
input(type='checkbox', ng-model='search.group[group._id]')
| {{group.name}}
h4=env.t('membership')
.radio
label
input(type='radio', name='search-participation-radio', ng-click='search._isMember = true')
=env.t('participating')
.radio
label
input(type='radio', name='search-participation-radio', ng-click='search._isMember = false')
=env.t('notParticipating')
.radio
label
input(type='radio', name='search-participation-radio', ng-click='search._isMember = undefined', checked='checked')
=env.t('either')
.col-md-10
// Creation form
button.btn.btn-success#create-challenge-btn(ng-click='create()', ng-hide='newChallenge')=env.t('createChallenge')
.create-challenge-from.well(ng-if='newChallenge')
form(ng-submit='save(newChallenge)')
div
input.btn.btn-success(type='submit', value=env.t('save'))
input.btn.btn-danger(type='button', ng-click='discard()', value=env.t('discard'))
select(ng-model='newChallenge.group', ng-required='required', name='Group', ng-options='g._id as g.name for g in groups')
.challenge-options
.form-group
input.form-control(type='text', ng-model='newChallenge.name', placeholder=env.t('challengeTitle'), required='required')
.option-medium
input.option-content(type='text', minlength="3", maxlength="16", ng-model='newChallenge.shortName', placeholder=env.t('challengeTag'), required)
a(target='_blank', href='http://habitrpg.wikia.com/wiki/Tags')
i.icon-question-sign(popover=env.t('challengeTagPop'), popover-trigger='mouseenter', popover-placement='right')
.form-group
input.form-control(type='text', minlength="3", maxlength="16", ng-model='newChallenge.shortName', placeholder=env.t('challengeTag'), required)
a(target='_blank', href='http://habitrpg.wikia.com/wiki/Tags')
span.glyphicon.glyphicon-question-sign(popover=env.t('challengeTagPop'), popover-trigger='mouseenter', popover-placement='right')
textarea.option-content(cols='3', placeholder=env.t('challengeDescr'), ng-model='newChallenge.description')
.form-group
textarea.form-control(cols='3', placeholder=env.t('challengeDescr'), ng-model='newChallenge.description')
//- what's going on here?
br
br
//- what's going on here?
br
br
.option-medium
input.option-content(type='number', min="{{newChallenge.group=='habitrpg' ? 1 : 0}}", max="{{maxPrize}}", ng-model='newChallenge.prize', placeholder=env.t('prize'))
span.input-suffix.Pet_Currency_Gem1x.inline-gems
i.icon-question-sign(popover=env.t('prizePop'), popover-trigger='mouseenter', popover-placement='right')
span(ng-show='newChallenge.group=="habitrpg"')
!=env.t('publicChallenges')
.form-group
input.form-control(type='number', min="{{newChallenge.group=='habitrpg' ? 1 : 0}}", max="{{maxPrize}}", ng-model='newChallenge.prize', placeholder=env.t('prize'))
span.input-suffix.Pet_Currency_Gem1x.inline-gems
span.glyphicon.glyphicon-question-sign(popover=env.t('prizePop'), popover-trigger='mouseenter', popover-placement='right')
span(ng-show='newChallenge.group=="habitrpg"')
!=env.t('publicChallenges')
.option-medium(ng-if='user.contributor.admin')
label.checkbox
input(type='checkbox', ng-model='newChallenge.official')
=env.t('officialChallenge')
.form-group(ng-if='user.contributor.admin')
.checkbox
label
input(type='checkbox', ng-model='newChallenge.official')
=env.t('officialChallenge')
habitrpg-tasks(main=false, obj='newChallenge')
habitrpg-tasks(main=false, obj='newChallenge')
// Challenges list
.accordion-group(ng-repeat='challenge in challenges|filter:filterChallenges')
.accordion-heading
ul.pull-right.challenge-accordion-header-specs
li(ng-if='challenge.official')
span.label.label-success=env.t('officialChallenge')
li {{challenge.group.name}}
li
=env.t('by')
strong {{challenge.leader.profile.name}}
li
| {{challenge.memberCount}}
= ' ' + env.t('participants')
li(ng-show='challenge.prize')
// prize
table(ng-show='challenge.prize')
tr
td {{challenge.prize}}
td
.Pet_Currency_Gem1x(style='margin-left:0px')
td=env.t('prize')
li
// leave / join
a.btn.btn-small.btn-danger(ng-show='challenge._isMember', ng-click='clickLeave(challenge, $event)')
i.icon-ban-circle
=env.t('leave')
a.btn.btn-small.btn-success(ng-hide='challenge._isMember', ng-click='join(challenge)')
i.icon-ok
=env.t('join')
a.accordion-toggle(ng-click='toggle(challenge._id)') {{challenge.name}}
.accordion-body(ng-class='{collapse: !$stateParams.cid == challenge._id}')
.accordion-inner(ng-if='$stateParams.cid == challenge._id')
div(ui-view)
// Challenges list
.panel-group
.panel.panel-default(ng-repeat='challenge in challenges|filter:filterChallenges')
.panel-heading
ul.pull-right.challenge-accordion-header-specs
li(ng-if='challenge.official')
span.label.label-success=env.t('officialChallenge')
li {{challenge.group.name}}
li
=env.t('by')
strong {{challenge.leader.profile.name}}
li
| {{challenge.memberCount}}
= ' ' + env.t('participants')
li(ng-show='challenge.prize')
p
// prize
| {{challenge.prize}}&nbsp;
span.inline-block.Pet_Currency_Gem1x
| &nbsp;
=env.t('prize')
li
// leave / join
a.btn.btn-sm.btn-danger(ng-show='challenge._isMember', ng-click='clickLeave(challenge, $event)')
span.glyphicon.glyphicon-ban-circle
=env.t('leave')
a.btn.btn-sm.btn-success(ng-hide='challenge._isMember', ng-click='join(challenge)')
span.glyphicon.glyphicon-ok
=env.t('join')
a.accordion-toggle(ng-click='toggle(challenge._id)') {{challenge.name}}
.panel-body(ng-class='{collapse: !$stateParams.cid == challenge._id}')
.accordion-inner(ng-if='$stateParams.cid == challenge._id')
div(ui-view)
+13 -15
View File
@@ -1,16 +1,14 @@
form.chat-form(ng-submit='postChat(group,message.content)')
.-options
//FIXME ng-model makes this painfully slow! using jquery for now, which is really non-angular-like
.control-group.option-large(ng-controller='AutocompleteCtrl')
textarea.chat-textarea.option-content(style='height:6em;', ui-keypress='{13:"postChat(group,message.content)"}', ng-model='message.content', updateinterval='250', flag='@', at-user, auto-complete)
span.user-list(ng-show='!isAtListHidden')
ul.list-at-user
li(bindonce='response', ng-repeat='user in response | filter:query.text | limitTo: 5', ng-click='autoComplete(user)')
span.username.label(class='label-contributor-{{user.contributor.level}}', ng-class='{"label-npc": user.backer.npc}') {{user.user}}
table.pull-right
tr
td
input.btn.chat-btn(type='submit', value=env.t('sendChat'), ng-class='{disabled: _sending == true}')
td
button.btn(type="button", ng-click='sync(group)', tooltip=env.t('toolTipMsg'))
i(class='pull-right icon-refresh')
div(ng-controller='AutocompleteCtrl')
textarea.form-control(rows=4, ui-keypress='{13:"postChat(group,message.content)"}', ng-model='message.content', updateinterval='250', flag='@', at-user, auto-complete)
span.user-list(ng-show='!isAtListHidden')
ul.list-at-user
li(bindonce='response', ng-repeat='user in response | filter:query.text | limitTo: 5', ng-click='autoComplete(user)')
span.username.label.label-default(ng-class='userLevelStyle(user)') {{user.user}}
.row
.col-md-9
include ../../shared/formatting-help
.col-md-3
input.btn.btn-default.chat-btn(type='submit', value=env.t('sendChat'), ng-class='{disabled: _sending == true}')
button.btn.btn-default(type="button", ng-click='sync(group)', tooltip=env.t('toolTipMsg'))
span.glyphicon.glyphicon-refresh.pull-right
+12 -10
View File
@@ -1,14 +1,16 @@
li.chat-message(bindonce='group.chat', ng-repeat='message in group.chat', bo-class='{highlight: isUserMentioned(user,message), "own-message": user._id == message.uuid}')
div(class='scrollable-message')
a.label.chat-message(class='hidden-label')
span {{message.user}}&nbsp;
markdown(ng-model='message.text', style='padding:0px 3px')
a.label.label-default.chat-message(class='hidden-label')
span {{message.user}}
| &nbsp;
markdown(ng-model='message.text')
| -
span.muted.time(from-now='message.timestamp',style='padding:0px 3px')
span(style='padding:0px 3px')
a.label(ng-show='countExists(message.likes)',ng-class='{"label-success":message.likes[user._id]}',ng-click='likeChatMessage(group,message)') +{{countExists(message.likes)}}
a.chat-plus-one.muted(ng-show='!countExists(message.likes)',ng-click='likeChatMessage(group,message)',style='background-color:#eee;padding:3px 3px 0px 3px') +1
a(bo-show='user.contributor.admin || message.uuid == user.id', ng-click='deleteChatMessage(group, message)',style='padding: 0px 3px')
i.icon-trash(tooltip=env.t('delete'))
a.label.chat-message(bo-show='message.user', class='float-label', bo-class='{"label-npc": message.backer.npc, "label-contributor-{{message.contributor.level}}":message.contributor.level}', ng-click='clickMember(message.uuid, true)')
span.muted.time(from-now='message.timestamp')
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.chat-plus-one.muted(ng-show='!countExists(message.likes)', ng-click='likeChatMessage(group,message)') +1
| &nbsp;
a(bo-show='user.contributor.admin || message.uuid == user.id', ng-click='deleteChatMessage(group, message)')
span.glyphicon.glyphicon-trash(tooltip=env.t('delete'))
a.label.label-default.chat-message(bo-show='message.user', class='float-label', bo-class='{"label-npc": message.backer.npc, "label-contributor-{{message.contributor.level}}":message.contributor.level}', ng-click='clickMember(message.uuid, true)')
span(tooltip='{{contribText(message.contributor, message.backer)}}') {{message.user}}&nbsp;
+24 -24
View File
@@ -1,27 +1,27 @@
form.form-horizontal(ng-submit='create(newGroup)')
.control-group.whatever-options
.control-group
label.control-label(for='new-group-name')
| {{text}}&nbsp;
=env.t('name')
.controls
input#new-group-name.input-medium.option-content(required, type='text', placeholder='{{text}} Name', ng-model='newGroup.name')
.control-group
label.control-label(for='new-group-description')=env.t('description')
.controls
textarea#new-group-description.option-content(cols='3', placeholder=env.t('description'), ng-model='newGroup.description')
.control-group(ng-show='type=="guild"')
.controls
label.radio
input(type='radio', name='new-group-privacy', value='public', ng-model='newGroup.privacy')
=env.t('public')
label.radio
input(type='radio', name='new-group-privacy', value='private', ng-model='newGroup.privacy')
=env.t('inviteOnly')
input.btn(type='submit', ng-disabled='!newGroup.privacy && !newGroup.name', value=env.t('create'))
.container-fluid
.row
form.col-md-12.form-horizontal(ng-submit='create(newGroup)')
.form-group
label.control-label(for='new-group-name')
| {{text}}&nbsp;
=env.t('name')
input.form-control#new-group-name.input-medium.option-content(required, type='text', placeholder=('{{text}} ' + env.t('name')), ng-model='newGroup.name')
.form-group
label(for='new-group-description')=env.t('description')
textarea.form-control#new-group-description.option-content(cols='3', placeholder=env.t('description'), ng-model='newGroup.description')
.form-group(ng-show='type=="guild"')
.radio
label
input(type='radio', name='new-group-privacy', value='public', ng-model='newGroup.privacy')
=env.t('public')
.radio
label
input(type='radio', name='new-group-privacy', value='private', ng-model='newGroup.privacy')
=env.t('inviteOnly')
br
input.btn.btn-default(type='submit', ng-disabled='!newGroup.privacy && !newGroup.name', value=env.t('create'))
span.gem-cost= '4 ' + env.t('gems')
p
small=env.t('gemCost')
.control-group(ng-show='type=="party"')
.controls
input.btn(type='submit', value=env.t('create'))
.form-group(ng-show='type=="party"')
input.btn.btn-default.form-control(type='submit', value=env.t('create'))
+164 -161
View File
@@ -4,174 +4,177 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title=env.t('guild
.Pet_Currency_Gem2x.Gems
| {{group.balance * 4 | number:0 }}
=env.t('guildGems')
.row-fluid
.span4
.container-fluid
.row
.col-md-4
// ------ Bosses -------
.modal.inline-modal(bindonce='group', ng-if='group.type==="party" && group.quest.key')
.modal-header
h3(ng-if='group.quest.active==false')=env.t('questInvitation') + '{{Content.quests[group.quest.key].text}}'
h3(ng-if='group.quest.active==true') {{Content.quests[group.quest.key].text}}
.modal-body
div(ng-if='group.quest.active==false')
table.table.table-striped
tr(ng-repeat='member in group.members')
td {{member.profile.name}}
td {{group.quest.members[member._id] === true ? env.t('accepted') : group.quest.members[member._id] === false ? env.t('rejected') : env.t('pending')}}
hr
.npc_ian.pull-left
p=env.t('questStart')
button.btn.btn-small.btn-warning(ng-if='!group.quest.leader || group.quest.leader==user._id', ng-click='party.$questAccept({"force":true})')=env.t('begin')
//-TODO Cancel button
//-TODO Both force-start & cancel should only be available to quest-initiator
div(ng-if='group.quest.active==true')
div(ng-if='Content.quests[group.quest.key].boss')
div(class="quest_{{group.quest.key}}")
//-
.progress(style="height:10px")
.bar(style='width: {{Shared.percent(group.quest.hp, Content.quests[group.quest.key].hp)}}%;')
span.meter-text
i.icon-heart
| {{group.quest.hp | number:0}} / {{Content.quests[group.quest.key].hp}}
.hero-stats
.meter.health(title=env.t('bossHP'))
.bar(style='width: {{Shared.percent(group.quest.progress.hp, Content.quests[group.quest.key].boss.hp)}}%;')
span.meter-text
i.icon-heart
| {{group.quest.progress.hp | number:0}} / {{Content.quests[group.quest.key].boss.hp}}
div(ng-if='Content.quests[group.quest.key].collect')
h4=env.t('collected') + ':'
// ------ Bosses -------
.panel.panel-default(bindonce='group', ng-if='group.type==="party" && group.quest.key')
.panel-heading
h3.panel-title(ng-if='group.quest.active==false')=env.t('questInvitation') + ' {{Content.quests[group.quest.key].text}}'
h3.panel-title(ng-if='group.quest.active==true') {{Content.quests[group.quest.key].text}}
.panel-body.modal-fixed-height
div(ng-if='group.quest.active==false')
table.table.table-striped
tr(ng-repeat='(k,v) in group.quest.progress.collect', class='quest_collected_{{v >= Content.quests[group.quest.key].collect[k].count}}')
td
div.pull-left(class='quest_{{group.quest.key}}_{{k}}')
| {{Content.quests[group.quest.key].collect[k].text}}
td
{{v}} / {{Content.quests[group.quest.key].collect[k].count}}
tr(ng-repeat='member in group.members')
td {{member.profile.name}}
td {{group.quest.members[member._id] === true ? env.t('accepted') : group.quest.members[member._id] === false ? env.t('rejected') : env.t('pending')}}
hr
.npc_ian.pull-left
p=env.t('questStart')
button.btn.btn-sm.btn-warning(ng-if='!group.quest.leader || group.quest.leader==user._id', ng-click='party.$questAccept({"force":true})')=env.t('begin')
//-TODO Cancel button
//-TODO Both force-start & cancel should only be available to quest-initiator
div(ng-bind-html='Content.quests[group.quest.key].notes')
div(ng-if='group.quest.active==true')
div(ng-if='Content.quests[group.quest.key].boss')
div(class="quest_{{group.quest.key}}")
//-
.progress
.bar(style='width: {{Shared.percent(group.quest.hp, Content.quests[group.quest.key].hp)}}%;')
span.meter-text
span.glyphicon.glyphicon-heart
| {{group.quest.hp | number:0}} / {{Content.quests[group.quest.key].hp}}
.hero-stats
.meter.health(title=env.t('bossHP'))
.bar(style='width: {{Shared.percent(group.quest.progress.hp, Content.quests[group.quest.key].boss.hp)}}%;')
span.meter-text
span.glyphicon.glyphicon-heart
| {{group.quest.progress.hp | number:0}} / {{Content.quests[group.quest.key].boss.hp}}
div(ng-if='Content.quests[group.quest.key].collect')
h4=env.t('collected') + ':'
table.table.table-striped
tr(ng-repeat='(k,v) in group.quest.progress.collect', class='quest_collected_{{v >= Content.quests[group.quest.key].collect[k].count}}')
td
div.pull-left(class='quest_{{group.quest.key}}_{{k}}')
| {{Content.quests[group.quest.key].collect[k].text}}
td
{{v}} / {{Content.quests[group.quest.key].collect[k].count}}
div(ng-bind-html='Content.quests[group.quest.key].notes')
hr
h5=env.t('participants')
table.table.table-striped
tr(ng-repeat='(k,v) in group.members', ng-show='group.quest.members[v._id]')
td {{v.profile.name}}
quest-rewards(key='{{group.quest.key}}')
div(ng-if='Content.quests[group.quest.key].boss')
.npc_ian.pull-left
p!=env.t('bossDmg1')
br
p=env.t('bossDmg2')
div(ng-if='Content.quests[group.quest.key].collect')
.npc_ian.pull-left
p=env.t('bossColl1')
br
p=env.t('bossColl2')
button.btn.btn-xs.btn-danger(ng-if='!group.quest.leader || group.quest.leader==user._id', ng-click='questAbort()')=env.t('abort')
// ------ Information -------
.panel.panel-default
.panel-heading(bindonce='group')
h3.panel-title
| {{group.name}}
span(ng-if='group.leader == user.id')
button.btn.btn-primary.pull-right(ng-click='save(group)', ng-show='group._editing')=env.t('save')
button.btn.btn-default.pull-right(ng-click='group._editing = true', ng-hide='group._editing')=env.t('editGroup')
.panel-body
form(ng-show='group._editing')
.form-group
label=env.t('groupName')
input.form-control(type='text', ng-model='group.name', placeholder=env.t('groupName'))
.form-group
label=env.t('description')
textarea.form-control(rows=6, ng-model='group.description')
include ../../shared/formatting-help
.form-group
label=env.t('logoUrl')
input.form-control(type='url', placeholder=env.t('logoUrl'), ng-model='group.logo')
h4=env.t('assignLeader')
select#group-leader-selection(ng-model='group._newLeader', ng-options='member.profile.name for member in group.members')
div(ng-show='!group._editing')
img.pull-right(ng-show='group.logo', ng-src='{{group.logo}}')
markdown(ng-model='group.description')
hr
h5=env.t('participants')
small.muted Group ID: {{group._id}}
include ./challenge-box
// ------ Members -------
.panel.panel-default
.panel-heading
h3.panel-title=env.t('members')
.panel-body.modal-fixed-height
span
=env.t('partyList')
|&nbsp;
select#partyOrder(
ng-model='user.party.order',
ng-controller='ChatCtrl',
ng-options='k as v for (k , v) in partyOrderChoices',
ng-change='set({"party.order": user.party.order})'
)
table.table.table-striped(bindonce='group')
tr(ng-repeat='member in group.members')
td
// allow leaders to ban members
div(ng-show='group.leader == user.id && user.id!=member._id')
a(ng-click='removeMember(group, member, true)')
span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip'))
a
span(ng-class='{"badge badge-info": group.leader==member._id}', ng-click='clickMember(member._id, true)')
| {{member.profile.name}}
tr(ng-if='group.memberCount > group.members.length')
td
|<span class='badge'>{{group.memberCount - group.members.length}}
= ' ' + env.t('moreMembers')
|</span>
h4(ng-show='group.invites.length > 0')=env.t('invited')
table.table.table-striped
tr(ng-repeat='(k,v) in group.members', ng-show='group.quest.members[v._id]')
td {{v.profile.name}}
quest-rewards(key='{{group.quest.key}}')
div(ng-if='Content.quests[group.quest.key].boss')
.npc_ian.pull-left
p!=env.t('bossDmg1')
br
p=env.t('bossDmg2')
div(ng-if='Content.quests[group.quest.key].collect')
.npc_ian.pull-left
p=env.t('bossColl1')
br
p=env.t('bossColl2')
tr(ng-repeat='invite in group.invites')
td
// allow leaders to ban members
div(ng-show='group.leader == user.id')
a(ng-click='removeMember(group, invite, false)')
span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip'))
a
span(ng-click='clickMember(invite._id, true)')
| {{invite.profile.name}}
.panel-footer
form.form-inline(ng-submit='invite(group)')
//.alert.alert-danger(ng-show='_groupError') {{_groupError}}
.form-group
input.form-control(type='text', placeholder=env.t('userId'), ng-model='group.invitee')
&nbsp;
input.btn.btn-default(type='submit', value=env.t('invite'))
button.btn.btn-mini.btn-danger(ng-if='!group.quest.leader || group.quest.leader==user._id', ng-click='questAbort()')=env.t('abort')
a.btn.btn-danger(data-id='{{group.id}}', ng-click='clickLeave(group, $event)')=env.t('leave')
// ------ Information -------
.modal.inline-modal
.modal-header(bindonce='group')
span(ng-if='group.leader == user.id')
button.btn.btn-primary.pull-right(ng-click='save(group)', ng-show='group._editing')=env.t('save')
button.btn.btn-default.pull-right(ng-click='group._editing = true', ng-hide='group._editing')=env.t('editGroup')
h3 {{group.name}}
.modal-body
div.whatever-options(ng-show='group._editing')
.control-group.option-large
label.control-labe=env.t('groupName')
input.option-content(type='text', ng-model='group.name', placeholder=env.t('groupName'))
.control-group.option-large
label.control-label=env.t('description')
textarea.option-content(style='height:15em;', placeholder=env.t('groupDescr'), ng-model='group.description')
.control-group.option-large
label.control-label=env.t('logoUrl')
input.option-content(type='url', placeholder=env.t('logoUrl'), ng-model='group.logo')
.col-md-8
div()
textarea.form-control(ng-show='group._editing', rows=6, placeholder=env.t('leaderMsg'), ng-model='group.leaderMessage')
table(ng-show='group.leaderMessage')
tr
td
.popover.static-popover.fade.right.in.wide-popover
.arrow
h3.popover-title {{Members.members[group.leader].profile.name}}
.popover-content
markdown(ng-model='group.leaderMessage')
div(ng-controller='ChatCtrl')
h3=env.t('chat')
include ./chat-box
h4=env.t('assignLeader')
select#group-leader-selection(ng-model='group._newLeader', ng-options='member.profile.name for member in group.members')
div(ng-show='!group._editing')
img.pull-right(ng-show='group.logo', style='max-width:150px', ng-src='{{group.logo}}')
markdown(ng-model='group.description')
hr
small.muted Group ID: {{group._id}}
include ./challenge-box
// ------ Members -------
.modal.inline-modal
.modal-header
h3=env.t('members')
.modal-body
span
=env.t('partyList')
select#partyOrder(
style='width:140px',
ng-model='user.party.order',
ng-controller='ChatCtrl',
ng-options='k as v for (k , v) in partyOrderChoices',
ng-change='set({"party.order": user.party.order})'
)
table.table.table-striped(bindonce='group')
tr(ng-repeat='member in group.members')
td
// allow leaders to ban members
div(ng-show='group.leader == user.id && user.id!=member._id')
a(ng-click='removeMember(group, member, true)')
i.icon-ban-circle(tooltip=env.t('banTip'))
a
span(ng-class='{"badge badge-info": group.leader==member._id}', ng-click='clickMember(member._id, true)')
| {{member.profile.name}}
tr(ng-if='group.memberCount > group.members.length')
td
|<span class='badge'>{{group.memberCount - group.members.length}}
= ' ' + env.t('moreMembers')
|</span>
h4(ng-show='group.invites.length > 0')=env.t('invited')
table.table.table-striped
tr(ng-repeat='invite in group.invites')
td
// allow leaders to ban members
div(ng-show='group.leader == user.id')
a(ng-click='removeMember(group, invite, false)')
i.icon-ban-circle(tooltip=env.t('banTip'))
a
span(ng-click='clickMember(invite._id, true)')
| {{invite.profile.name}}
.modal-footer.whatever-options
form.form-inline(ng-submit='invite(group)')
//.alert.alert-danger(ng-show='_groupError') {{_groupError}}
.control-group.option-large
input.option-content(type='text', placeholder=env.t('userId'), ng-model='group.invitee')
input.btn(type='submit', value=env.t('invite'))
a.btn.btn-danger(data-id='{{group.id}}', ng-click='clickLeave(group, $event)')=env.t('leave')
.span8
div.blah-options(ng-show='group._editing')
.option-large
textarea.option-content(style='height:15em;', placeholder=env.t('leaderMsg'), ng-model='group.leaderMessage')
table(ng-show='group.leaderMessage')
tr
td
.popover.static-popover.fade.right.in.wide-popover
.arrow
h3.popover-title {{Members.members[group.leader].profile.name}}
.popover-content
markdown(ng-model='group.leaderMessage')
div(ng-controller='ChatCtrl')
h3=env.t('chat')
include ./chat-box
include ../../shared/formatting-help
ul.unstyled.tavern-chat
include ./chat-message
ul.list-unstyled.tavern-chat
include ./chat-message
+65 -65
View File
@@ -12,83 +12,83 @@ script(type='text/ng-template', id='partials/options.social.hall.html')
div(ui-view)
script(type='text/ng-template', id='partials/options.social.hall.heroes.html')
div.well(ng-if='user.contributor.admin')
.well(ng-if='user.contributor.admin')
h2=env.t('rewardUser')
form.form-horizontal(ng-submit='loadHero(_heroID)')
.-options
.option-group.option-large
input.option-content(type='text', ng-model='_heroID', placeholder=env.t('UUID'))
button.btn(type='submit')=env.t('loadUser')
form.form-horizontal(ng-show='hero', ng-submit='saveHero(hero)')
form(ng-submit='loadHero(_heroID)')
.form-group
input.form-control(type='text', ng-model='_heroID', placeholder=env.t('UUID'))
.form-group
input.btn.btn-default(type='submit')=env.t('loadUser')
form(ng-show='hero', ng-submit='saveHero(hero)')
h3 {{hero.profile.name}}
.-options
.control-group.option-large
input.option-content(type='text', ng-model='hero.contributor.text', placeholder=env.t('contribTitle'))
.control-group.option-medium
input.option-content(type='number', step="any", ng-model='hero.contributor.level')
span.input-suffix=env.t('contribLevel')
br
small=env.t('contribHallText')
a(target='_blank', href='https://trello.com/c/wkFzONhE/277-contributor-gear')=env.t('moreDetails')
.control-group.option-large
textarea.option-content(style='height:15em;', placeholder=env.t('contributions'), ng-model='hero.contributor.contributions')
//-include ../../shared/formatting-help
.control-group.option-medium
label.checkbox
.form-group
input.form-control(type='text', ng-model='hero.contributor.text', placeholder=env.t('contribTitle'))
.form-group
label=env.t('contribLevel')
input.form-control(type='number', step="any", ng-model='hero.contributor.level')
small=env.t('contribHallText')
a(target='_blank', href='https://trello.com/c/wkFzONhE/277-contributor-gear')=env.t('moreDetails')
.form-group
textarea.form-control(cols=5, placeholder=env.t('contributions'), ng-model='hero.contributor.contributions')
include ../../shared/formatting-help
.form-group
.checkbox
label
input(type='checkbox', ng-model='hero.contributor.admin')
=env.t('admin')
hr
.control-group.option-medium
input.option-content(type='number', step="any", ng-model='hero.balance')
span.input-suffix=env.t('balance')
p
small!= '`user.balance`' + env.t('notGems')
.form-group
label=env.t('balance')
input.form-control(type='number', step="any", ng-model='hero.balance')
small!= '`user.balance`' + env.t('notGems')
.control-group.option-medium
label.checkbox
input(type='checkbox', ng-model='hero.purchased.ads')
=env.t('hideAds')
.form-group
.checkbox
label
input(type='checkbox', ng-model='hero.purchased.ads')
=env.t('hideAds')
// h4 Backer Status
// Add backer stuff like tier, disable adds, etcs
button.btn-primary(type='submit')=env.t('save')
// h4 Backer Status
// Add backer stuff like tier, disable adds, etcs
.form-group
input.form-control.btn.btn-primary(type='submit')=env.t('save')
table.table.table-striped
tr
thead
tr
th=env.t('name')
th(ng-if='user.contributor.admin')=env.t('UUID')
th=env.t('contribLevel')
th=env.t('title')
th=env.t('contributions')
tr(ng-repeat='hero in heroes')
td
span(ng-if='hero.contributor.admin',popover=env.t('gamemaster'),popover-trigger='mouseenter',popover-placement='right')
a.label(class='label-contributor-{{hero.contributor.level}}', ng-class='{"label-npc": hero.backer.npc}', ng-click='clickMember(hero._id, true)')
| {{hero.profile.name}}&nbsp;
i.icon-star.icon-white
span(ng-if='!hero.contributor.admin')
a.label(class='label-contributor-{{hero.contributor.level}}', ng-class='{"label-npc": hero.backer.npc}', ng-click='clickMember(hero._id, true)') {{hero.profile.name}}
td(ng-if='user.contributor.admin') {{hero._id}}
td {{hero.contributor.level}}
td {{hero.contributor.text}}
td
markdown(ng-model='hero.contributor.contributions', target='_blank')
thead
tr
th=env.t('name')
th(ng-if='user.contributor.admin')=env.t('UUID')
th=env.t('contribLevel')
th=env.t('title')
th=env.t('contributions')
tbody
tr(ng-repeat='hero in heroes')
td
span(ng-if='hero.contributor.admin',popover=env.t('gamemaster'),popover-trigger='mouseenter',popover-placement='right')
a.label.label-default(ng-class='userLevelStyle(hero)', ng-click='clickMember(hero._id, true)')
| {{hero.profile.name}}&nbsp;
span.glyphicon.glyphicon-star
span(ng-if='!hero.contributor.admin')
a.label.label-default(ng-class='userLevelStyle(hero)', ng-click='clickMember(hero._id, true)') {{hero.profile.name}}
td(ng-if='user.contributor.admin') {{hero._id}}
td {{hero.contributor.level}}
td {{hero.contributor.text}}
td
markdown(ng-model='hero.contributor.contributions', target='_blank')
script(type='text/ng-template', id='partials/options.social.hall.patrons.html')
table.table.table-striped(infinite-scroll="loadMore()")
tr
thead
tr
th=env.t('name')
th(ng-if='user.contributor.admin')=env.t('UUID')
th=env.t('backerTier')
tr(ng-repeat='patron in patrons')
td
a.label(class='label-contributor-{{patron.contributor.level}}', ng-class='{"label-npc": patron.backer.npc}', ng-click='clickMember(patron._id, true)') {{patron.profile.name}}
td(ng-if='user.contributor.admin') {{patron._id}}
td {{patron.backer.tier}}
thead
tr
th=env.t('name')
th(ng-if='user.contributor.admin')=env.t('UUID')
th=env.t('backerTier')
tbody
tr(ng-repeat='patron in patrons')
td
a.label.label-default(ng-class='userLevelStyle(patron)', ng-click='clickMember(patron._id, true)') {{patron.profile.name}}
td(ng-if='user.contributor.admin') {{patron._id}}
td {{patron.backer.tier}}
+14 -12
View File
@@ -7,13 +7,12 @@ include ./hall.jade
script(type='text/ng-template', id='partials/options.social.tavern.html')
include ./tavern
script(type='text/ng-template', id='partials/options.social.party.html')
div(ng-show='group._id')
include ./group
div(ng-hide='group._id')
div(ng-show='user.invitations.party.id')
h2=env.t('invitedTo') + '{{user.invitations.party.name}}'
h2=env.t('invitedTo') + ' {{user.invitations.party.name}}'
a.btn.btn-success(data-type='party', ng-click='join(user.invitations.party)')=env.t('accept')
a.btn.btn-danger(ng-click='reject()')=env.t('reject')
div(ng-hide='user.invitations.party.id')
@@ -30,12 +29,12 @@ script(type='text/ng-template', id='partials/options.social.party.html')
script(type='text/ng-template', id='partials/options.social.guilds.public.html')
div(ng-repeat='invitation in user.invitations.guilds')
h3=env.t('invitedTo') + '{{invitation.name}}'
h3=env.t('invitedTo') + ' {{invitation.name}}'
a.btn.btn-success(data-type='guild', ng-click='join(invitation)')=env.t('accept')
a.btn.btn-danger(ng-click='reject(invitation)')=env.t('reject')
// Public Groups
.options-group.option-large.whatever-options
input.option-content(type='text',ng-model='guildSearch', placeholder=env.t('search'))
.col-md-3
input.form-control(type='text',ng-model='guildSearch', placeholder=env.t('search'))
table.table.table-striped
tr(ng-repeat='group in groups.public | filter:guildSearch')
td
@@ -43,11 +42,11 @@ script(type='text/ng-template', id='partials/options.social.guilds.public.html')
li='{{group.memberCount}} ' + env.t('members')
li
// join / leave
a.btn.btn-small.btn-danger(ng-show='group._isMember', ng-click='clickLeave(group, $event)')
i.icon-ban-circle
a.btn.btn-sm.btn-danger(ng-show='group._isMember', ng-click='clickLeave(group, $event)')
span.glyphicon.glyphicon-ban-circle
=env.t('leave')
a.btn.btn-small.btn-success(ng-hide='group._isMember', ng-click='join(group)')
i.icon-ok
a.btn.btn-sm.btn-success(ng-hide='group._isMember', ng-click='join(group)')
span.glyphicon.glyphicon-ok
=env.t('join')
h4 {{group.name}}
p {{group.description}}
@@ -78,7 +77,8 @@ script(type='text/ng-template', id='partials/options.social.html')
ul.nav.nav-tabs
li(ng-class="{ active: $state.includes('options.social.tavern') }")
a(ui-sref='options.social.tavern')
i.icon-eye-close
span.glyphicon.glyphicon-eye-close
| &nbsp;
=env.t('tavern')
li(ng-class="{ active: $state.includes('options.social.party') }")
a(ui-sref='options.social.party')
@@ -88,11 +88,13 @@ script(type='text/ng-template', id='partials/options.social.html')
=env.t('guilds')
li(ng-class="{ active: $state.includes('options.social.challenges') }")
a(ui-sref='options.social.challenges')
i.icon-bullhorn
span.glyphicon.glyphicon-bullhorn
| &nbsp;
=env.t('challenges')
li(ng-class="{ active: $state.includes('options.social.hall') }")
a(ui-sref='options.social.hall')
i.icon-flag
span.glyphicon.glyphicon-flag
| &nbsp;
=env.t('hall')
.tab-content
+106 -109
View File
@@ -1,6 +1,6 @@
.row-fluid
.span4
.tavern-pane
.container-fluid
.row
.col-md-4
table
tr
td
@@ -13,115 +13,112 @@
.popover-content
=env.t('danielText')
div
button.btn.btn-large.btn-success(ng-class='{active: user.preferences.sleep}', ng-click='User.user.ops.sleep({})')
button.btn.btn-lg.btn-success(ng-class='{active: user.preferences.sleep}', ng-click='User.user.ops.sleep({})')
span(ng-show='user.preferences.sleep')=env.t('innCheckOut')
span(ng-hide='user.preferences.sleep')=env.t('innCheckIn')
.alert.alert-info(ng-show='user.preferences.sleep')
=env.t('innText')
.alert.alert-info(ng-show='user.preferences.sleep')
=env.t('innText')
// Resources
.modal(style='position: relative;top: auto;left: auto;right: auto;margin: 0 auto 20px;z-index: 1;max-width: 100%;')
.modal-header
h3=env.t('resources')
.modal-body
table.table.table-striped
tr
td
a(target='_blank', href='http://habitrpg.wikia.com/wiki/Board:Looking_for_a_Group_(LFG)')=env.t('lfgPosts')
tr
td
a(target='_blank', href='https://vimeo.com/57654086')=env.t('tutorial')
tr
td
a(target='_blank', href='http://habitrpg.wikia.com/wiki/FAQ')=env.t('FAQ')
tr
td
a(target='_blank', href='http://habitrpg.wikia.com/wiki/Glossary')=env.t('glossary')
tr
td
a(target='_blank', href='https://github.com/HabitRPG/habitrpg/issues')=env.t('reportAP')
tr
td
a(target='_blank', href='https://trello.com/c/8gzGlle8')=env.t('requestAF')
tr
td
a(target='_blank', href='http://habitrpg.wikia.com/wiki/Special:Forum')=env.t('community')
// Resources
.panel.panel-default
.panel-heading
h3.panel-title=env.t('resources')
.panel-body
table.table.table-striped
tr
td
a(target='_blank', href='http://habitrpg.wikia.com/wiki/Board:The_Archery_-_LFG')=env.t('lfgPosts')
tr
td
a(target='_blank', href='https://vimeo.com/57654086')=env.t('tutorial')
tr
td
a(target='_blank', href='http://habitrpg.wikia.com/wiki/FAQ')=env.t('FAQ')
tr
td
a(target='_blank', href='http://habitrpg.wikia.com/wiki/Glossary')=env.t('glossary')
tr
td
a(target='_blank', href='https://github.com/HabitRPG/habitrpg/issues/2760')=env.t('reportAP')
tr
td
a(target='_blank', href='https://trello.com/c/8gzGlle8')= ' ' + env.t('requestAF')
tr
td
a(target='_blank', href='http://habitrpg.wikia.com/wiki/Special:Forum')=env.t('community')
// Player Tiers
.modal(style='position: relative;top: auto;left: auto;right: auto;margin: 0 auto 20px;z-index: 1;max-width: 100%;', popover=env.t('tierPop'), popover-trigger="mouseenter", popover-placement="right")
.modal-header
h3=env.t('playerTiers')
.modal-body
small
|<a href='/#/options/groups/hall'>
=env.t('visitHeroes')
|</a> <br />
|<a href='http://habitrpg.wikia.com/wiki/Contributor_Rewards' target='_blank'>
=env.t('conLearn')
|</a> <br />
|<a href='http://habitrpg.wikia.com/wiki/Contributing_to_HabitRPG' target='_blank'>
=env.t('conLearnHow')
|</a>
table.table.table-striped
tr
td
a.label.label-contributor-1(ng-click='toggleUserTier($event)')=env.t('friendBadge')
div(style='display:none;')
p
span.achievement.achievement-firefox
!=env.t('friendFirst')
hr
p
span.shop-sprite.item-img(class='shop_armor_special_1')
!=env.t('friendSecond')
tr
td
a.label.label-contributor-3(ng-click='toggleUserTier($event)')=env.t('eliteBadge')
div(style='display:none;')
p
span.shop-sprite.item-img(class='shop_head_special_1')
!=env.t('eliteThird')
hr
p
span.shop-sprite.item-img(class='shop_weapon_special_1')
!=env.t('eliteFourth')
tr
td
a.label.label-contributor-5(ng-click='toggleUserTier($event)')=env.t('championBadge')
div(style='display:none;')
p
span.shop-sprite.item-img(class='shop_shield_special_1')
!=env.t('championFifth')
hr
p
div(class='Pet-Dragon-Hydra pull-left')
!=env.t('championSixth')
tr
td
a.label.label-contributor-7(ng-click='toggleUserTier($event)')=env.t('legendaryBadge')
div(style='display:none;')
p
!=env.t('legSeventh')
tr
td
a.label.label-contributor-8(ng-click='toggleUserTier($event)')=env.t('heroicBadge')
div(style='display:none;')
p=env.t('heroicText')
tr
td
a.label.label-npc(ng-click='toggleUserTier($event)')=env.t('npc')
div(style='display:none;')
p=env.t('npcText')
// Player Tiers
.panel.panel-default(popover=env.t('tierPop'), popover-trigger="mouseenter", popover-placement="right")
.panel-heading
h3.panel-title=env.t('playerTiers')
.panel-body
small
|<a href='/#/options/groups/hall'>
=env.t('visitHeroes')
|</a> <br />
|<a href='http://habitrpg.wikia.com/wiki/Contributor_Rewards' target='_blank'>
=env.t('conLearn')
|</a> <br />
|<a href='http://habitrpg.wikia.com/wiki/Contributing_to_HabitRPG' target='_blank'>
=env.t('conLearnHow')
|</a>
table.table.table-striped.panel-tiers
tr
td
a.label.label-contributor-1(ng-click='toggleUserTier($event)')=env.t('friend') + ' (1-2)'
div
p
span.achievement.achievement-firefox
!=env.t('friendFirst')
hr
p
span.shop-sprite.item-img(class='shop_armor_special_1')
!=env.t('friendSecond')
tr
td
a.label.label-contributor-3(ng-click='toggleUserTier($event)')=env.t('elite') + ' (3-4)'
div
p
span.shop-sprite.item-img(class='shop_head_special_1')
!=env.t('eliteThird')
hr
p
span.shop-sprite.item-img(class='shop_weapon_special_1')
!=env.t('eliteFourth')
tr
td
a.label.label-contributor-5(ng-click='toggleUserTier($event)')=env.t('champion') + ' (5-6)'
div
p
span.shop-sprite.item-img(class='shop_shield_special_1')
!=env.t('championFifth')
hr
p
div(class='Pet-Dragon-Hydra pull-left')
!=env.t('championSixth')
tr
td
a.label.label-contributor-7(ng-click='toggleUserTier($event)')=env.t('legendary') + ' (7)'
div
p
!=env.t('legSeventh')
tr
td
a.label.label-contributor-8(ng-click='toggleUserTier($event)')=env.t('heroic')
div
p=env.t('heroicText')
tr
td
a.label.label-npc(ng-click='toggleUserTier($event)')=env.t('npc')
div
p=env.t('npcText')
include ./challenge-box
include ./challenge-box
.span8(ng-controller='ChatCtrl')
h3=env.t('tavernTalk')
include ./chat-box
small(style='position: relative; top: -20px; left: 25px;')
include ../../shared/formatting-help
small(style='position: relative; top: 18px; left: 0px')
alert.alert-info
!=env.t('tavernAlert1') + ' <a href="https://github.com/HabitRPG/habitrpg/issues" target="_blank">' + env.t('tavernAlert2') + '</a>.'
ul.unstyled.tavern-chat
include ./chat-message
.col-md-8.tavern(ng-controller='ChatCtrl')
h3=env.t('tavernTalk')
include ./chat-box
.alert.alert-info.alert-sm
!= ' ' + env.t('tavernAlert1') + ' <a href="https://github.com/HabitRPG/habitrpg/issues/2760" target="_blank">' + env.t('tavernAlert2') + '</a>.'
ul.list-unstyled.tavern-chat
include ./chat-message
+19 -22
View File
@@ -1,20 +1,21 @@
footer.footer(ng-controller='FooterCtrl')
.container
.row
.span3
.col-sm-3
h4=env.t('footerMobile')
ul.unstyled
ul.list-unstyled
li
a(href='https://itunes.apple.com/us/app/habitrpg/id689569235?mt=8', target='_blank')=env.t('mobileIOS')
li
a(href='https://play.google.com/store/apps/details?id=com.ocdevel.habitrpg', target='_blank')=env.t('mobileAndroid')
.span3
.col-sm-3
h4=env.t('footerCompany')
ul.unstyled
if (!env.isFrontPage)
ul.list-unstyled
if (!env.isStaticPage)
li
.btn.btn-small.btn-success(ng-click='modals.buyGems = true')
i.icon-heart.icon-white
.btn.btn-sm.btn-success(ng-click='openModal("buyGems",{track:"Gems > Donate"})')
span.glyphicon.glyphicon-heart
|&nbsp;
=env.t('companyDonate')
li
a(href='/static/about')=env.t('companyAbout')
@@ -28,9 +29,9 @@ footer.footer(ng-controller='FooterCtrl')
a(href='/static/privacy')=env.t('companyPrivacy')
li
a(href='/static/terms')=env.t('companyTerms')
.span3
.col-sm-3
h4=env.t('footerCommunity')
ul.unstyled
ul.list-unstyled
li
a(target='_blank', href='https://github.com/HabitRPG/habitrpg/issues')=env.t('communityBug')
li
@@ -47,8 +48,8 @@ footer.footer(ng-controller='FooterCtrl')
a(target='_blank', href='https://www.facebook.com/Habitrpg')=env.t('communityFacebook')
li
a(target='_blank', href='http://www.reddit.com/r/habitrpg/')=env.t('communityReddit')
.span3
if (env.NODE_ENV=='production')
.col-sm-3
if (env.NODE_ENV == 'production' && !env.IS_MOBILE)
h4=env.t('footerSocial')
.addthis_toolbox.addthis_default_style(addthis:url='https://habitrpg.com', addthis:title=env.t('socialTitle'))
table
@@ -59,21 +60,17 @@ footer.footer(ng-controller='FooterCtrl')
td
a.addthis_button_tweet(tw:via='habitrpg')
tr
td(align='left')
td
iframe(src='/bower_components/github-buttons/github-btn.html?user=lefnire&repo=habitrpg&type=watch&count=true', allowtransparency='true', frameborder='0', scrolling='0', width='85px', height='20px')
tr
td
a.addthis_button_google_plusone(g:plusone:size='medium')
else
else if(env.NODE_ENV != 'production' && !env.isStaticPage)
h4 Debug
ul.unstyled
li
a.btn(ng-click='addMissedDay()') +1 Missed Day
li
a.btn(ng-click='addTenGems()') +10 Gems
li
a.btn(ng-click='addLevelsAndGold()') +Exp +GP
li
a.btn(ng-click='addOneLevel()') +1 Level
.btn-group-vertical
a.btn.btn-default(ng-click='addMissedDay()') +1 Missed Day
a.btn.btn-default(ng-click='addTenGems()') +10 Gems
a.btn.btn-default(ng-click='addLevelsAndGold()') +Exp +GP
a.btn.btn-default(ng-click='addOneLevel()') +1 Level
div(ng-init='deferredScripts()')
+1 -1
View File
@@ -1,4 +1,4 @@
a.pull-right.gem-wallet(ng-click='modals.buyGems = true', popover-trigger='mouseenter', popover-title=env.t('gems'), popover=env.t('gemsWhatFor'), popover-placement='bottom')
a.pull-right.gem-wallet(ng-click='openModal("buyGems",{track:"Gems > Wallet"})', popover-trigger='mouseenter', popover-title=env.t('gems'), popover=env.t('gemsWhatFor'), popover-placement='bottom')
span.task-action-btn.tile.flush.bright.add-gems-btn
span.task-action-btn.tile.flush.neutral
.Pet_Currency_Gem2x.Gems
+7 -5
View File
@@ -3,7 +3,7 @@
Removing it will remove the user's name, but will allow more room for mounts & helms. IMO this is the lesser of two evils, revisit
//-figure.herobox(ng-click='clickMember(profile._id)', data-name='{{profile.profile.name}}', ng-class='{isUser: profile.id==user.id, hasPet: profile.items.currentPet}', data-level='{{profile.stats.lvl}}', data-uid='{{profile.id}}', rel='popover', data-placement='bottom', data-trigger='hover', data-html='true', data-content="<div ng-hide='profile.id == user.id'> <div class='progress progress-danger' style='height:5px;'> <div class='bar' style='height: 5px; width: {{percent(profile.stats.hp, 50)}}%;'></div> </div> <div class='progress progress-warning' style='height:5px;'> <div class='bar' style='height: 5px; width: {{percent(profile.stats.exp, tnl(profile.stats.lvl))}}%;'></div> </div> <div>Level: {{profile.stats.lvl}}</div> <div>GP: {{profile.stats.gp | number:0}}</div> <div>{{count(profile.items.pets)}} / 90 Pets Found</div> </div>")
figure.herobox(ng-click='spell ? castEnd(profile, "user", $event) : clickMember(profile._id)', data-name='{{profile.profile.name}}', ng-class='{isUser: (user._id==profile._id && !user.items.currentMount), hasPet: profile.items.currentMount, "cast-target": applyingAction, isLeader: party.leader==profile._id}')
figure.herobox(ng-click='spell ? castEnd(profile, "user", $event) : clickMember(profile._id)', data-name='{{profile.profile.name}}', ng-class='{isUser: user._id==profile._id, hasPet:profile.items.currentPet, hasMount:profile.items.currentMount, "cast-target": applyingAction, isLeader: party.leader==profile._id}')
.character-sprites
// Mount Body
span(ng-if='profile.items.currentMount', class='Mount_Body_{{profile.items.currentMount}}')
@@ -43,8 +43,10 @@ figure.herobox(ng-click='spell ? castEnd(profile, "user", $event) : clickMember(
span(ng-class='{zzz:profile.preferences.sleep}')
// FIXME handle @minimal, this might have to be a directive
span.current-pet(class='Pet-{{profile.items.currentPet}}', ng-show='profile.items.currentPet && !minimal')
.avatar-level(ng-class='userLevelStyle(profile,"label")')
i.icon-circle-arrow-up.icon-white(ng-show='profile.stats.buffs.str || profile.stats.buffs.per || profile.stats.buffs.con || profile.stats.buffs.int || profile.stats.buffs.stealth', tooltip=env.t('buffed'), style='margin-right:5px;')
.avatar-level.label.label-default(ng-class='userLevelStyle(profile)')
span.glyphicon.glyphicon-circle-arrow-up(ng-show='profile.stats.buffs.str || profile.stats.buffs.per || profile.stats.buffs.con || profile.stats.buffs.int || profile.stats.buffs.stealth', tooltip=env.t('buffed'), style='margin-right:5px;')
|&nbsp;
=env.t('lvl')
| {{profile.stats.lvl}}
i.icon-plus-sign.icon-white(ng-show='profile.achievements.rebirths', tooltip=env.t('reborn', {reLevel: "{{profile.achievements.rebirthLevel}}"}), style='margin-left:5px')
|&nbsp;{{profile.stats.lvl}}
|&nbsp;
span.glyphicon.glyphicon-plus-sign(ng-show='profile.achievements.rebirths', tooltip=env.t('reborn', {reLevel: "{{profile.achievements.rebirthLevel}}"}))
+9 -10
View File
@@ -1,7 +1,5 @@
.header-wrap(ng-controller='HeaderCtrl')
a.label.undo-button(x-bind='click:undo', ng-show='_undo')=env.t('undo')
div(ng-if='!user.preferences.hideHeader')
include menu
a.label.label-default.undo-button(x-bind='click:undo', ng-show='_undo')=env.t('undo')
header.site-header(ng-hide='user.preferences.hideHeader', role='banner', data-partysize='{{party.members.length>1 ? truarr(party.members.length) : 0}}')
// avatar
@@ -13,27 +11,28 @@
.meter.health(title=env.t('health'))
.bar(style='width: {{Shared.percent(user.stats.hp, 50)}}%;')
span.meter-text
i.icon-heart
span.glyphicon.glyphicon-heart
| &nbsp;
| {{Math.ceil(user.stats.hp)}} / 50
.meter.experience(title=env.t('experience'))
.bar(style='width: {{Shared.percent(user.stats.exp,Shared.tnl(user.stats.lvl))}}%;')
span.meter-text
i.icon-star
span.glyphicon.glyphicon-star
| &nbsp;
| {{Math.floor(user.stats.exp)}} / {{Shared.tnl(user.stats.lvl) | number:0}}
// FIXME doesn't look great here, but the "Experience" CSS title rollover covers it where it was before
span(ng-show='user.history.exp')
a(ng-click='toggleChart("exp")', tooltip=env.t('progress'))
i.icon-signal
| &nbsp;
span.glyphicon.glyphicon-signal
.meter.mana(title='Mana', ng-if='user.flags.classSelected && !user.preferences.disableClasses')
.bar(style='width: {{user.stats.mp / user._statsComputed.maxMP * 100}}%;')
span.meter-text
i.icon-fire
span.glyphicon.glyphicon-fire
| &nbsp;
| {{Math.floor(user.stats.mp)}} / {{user._statsComputed.maxMP}}
// party
span(ng-controller='PartyCtrl')
.herobox-wrap(ng-repeat='profile in partyMinusSelf')
include avatar
.npc_bailey.npc_bailey_head(ng-show='user.flags.newStuff', tooltip=env.t('psst'), tooltip-placement='top', ng-click='modals.newStuff=true')
+132 -36
View File
@@ -1,36 +1,132 @@
.user-menu(ng-controller='AuthCtrl')
button.task-action-btn.tile.solid(ng-hide='authenticated()', style='cursor: pointer;', ng-click="modals.login = true")=env.t('loginAndReg')
ul.nav.site-nav(ng-show='authenticated()')
li.flyout
h1.task-action-btn.tile.solid.user-reporter {{user.profile.name}}
ul.flyout-content.nav.stacked
li
a.task-action-btn.tile.solid
span(ng-show='$state.includes("options")', ui-sref='tasks')
i.icon-ok
=env.t('tasks')
span(ng-show='$state.includes("tasks")', ui-sref='options')
i.icon.icon-wrench
=env.t('options')
li
a.task-action-btn.tile.solid(href="http://habitrpg.wikia.com/wiki/FAQ", target='_blank')
i.icon.icon-book
=env.t('FAQ')
li
a.task-action-btn.tile.solid(href="https://vimeo.com/57654086", target='_blank')
i.icon.icon-film
=env.t('tutorials')
li
a.task-action-btn.tile.solid(ng-click='User.sync()')
i.icon.icon-refresh
=env.t('sync')
li
a.task-action-btn.tile.solid(ng-click='logout()')
i.icon-share-alt
=env.t('logout')
// party invitation notification
a(ng-show='user.party.invitation', style='cursor: pointer;', x-bind='click:gotoPartyChat')
i.icon-user
// party chat notification
a(ng-show='newChatMessages(_party.chat,user.party.lastMessageSeen)', style='cursor: pointer;', x-bind='click:gotoPartyChat')
i.icon-comment(tooltip=env.t('partyNotification'))
a.collapse-toolbar(popover='Expand Toolbar',popover-placement='left',popover-trigger='mouseenter',ng-if='user.preferences.toolbarCollapsed',ng-click='set({"preferences.toolbarCollapsed":false})')
span.glyphicon.glyphicon-chevron-down
nav.navbar.navbar-inverse(ng-controller='AuthCtrl',style='min-height:0px;margin-bottom:0px;border:0;border-radius:0px',ng-if='!user.preferences.toolbarCollapsed')
ul.nav.navbar-nav.pull-right
li
a(popover='Collapse Toolbar',popover-placement='left',popover-trigger='mouseenter',,ng-if='!user.preferences.toolbarCollapsed',ng-click='set({"preferences.toolbarCollapsed":true})')
span.glyphicon.glyphicon-chevron-up.white
.navbar-header
button.navbar-toggle(type='button', ng-click='isNavbarCollapsed = !isNavbarCollapsed', ng-init='isNavbarCollapsed = true')
span.sr-only Toggle navigation
span.icon-bar
span.icon-bar
span.icon-bar
.collapse.navbar-collapse(collapse="isNavbarCollapsed")
ul.nav.navbar-nav
li
a(ui-sref='tasks',popover=env.t('tasks'),popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-check.white
li.dropdown
a.dropdown-toggle(popover='User',popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-user.white
span.caret
ul.dropdown-menu
li
a(ui-sref='options.profile.avatar')=env.t('avatar')
li
a(ui-sref='options.profile.stats')=env.t('stats')
li
a(ui-sref='options.profile.profile')=env.t('profile')
li
a(ng-click='logout()')
span.glyphicon.glyphicon-share-alt
| &nbsp;
=env.t('logout')
li.dropdown
a.dropdown-toggle(popover='Social',popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-heart.white
span.caret
ul.dropdown-menu
li
a(ui-sref='options.social.tavern')=env.t('tavern')
li
a(ui-sref='options.social.party')=env.t('party')
li
a(ui-sref='options.social.guilds')=env.t('guilds')
li
a(ui-sref='options.social.challenges')=env.t('challenges')
li
a(ui-sref='options.social.hall')=env.t('hall')
li.dropdown
a.dropdown-toggle(popover='Inventory',popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-gift.white
span.caret
ul.dropdown-menu
li
a(ui-sref='options.inventory.drops')=env.t('market')
li
a(ui-sref='options.inventory.pets')=env.t('mounts')
li
a(ui-sref='options.inventory.mounts')=env.t('mounts')
li
a(ui-sref='options.inventory.equipment')=env.t('equipment')
li.dropdown
a.dropdown-toggle(popover='Settings',popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-wrench.white
span.caret
ul.dropdown-menu
li
a(ui-sref='options.settings.settings')=env.t('site')
li
a(ui-sref='options.settings.api')=env.t('API')
li
a(ui-sref='options.settings.export')=env.t('export')
li
a(ui-sref='options.settings.subscription')=env.t('subscription')
li.dropdown
a.dropdown-toggle(popover='Help',popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-question-sign.white
span.caret
ul.dropdown-menu
li
a(href="http://habitrpg.wikia.com/wiki/FAQ", target='_blank')
span.glyphicon.glyphicon-book
| &nbsp;
=env.t('FAQ')
li
a(href="https://vimeo.com/57654086", target='_blank')
span.glyphicon.glyphicon-film
//| &nbsp; Tutorials is too long to stay in the menu if we add a space
=env.t('tutorials')
li
a(ng-click='User.sync()',popover=env.t('sync'),popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-refresh.white
//-| &nbsp;
//-=env.t('sync')
// Separator
li
a(style='border-right:1px solid #999;cursor:default;padding-left:0px;padding-right:0px') &nbsp;
li
a.pull-right.gem-wallet(style='padding-top:8px;padding-bottom:0px;',ng-click='openModal("buyGems",{track:"Gems > Toolbar"})', popover-trigger='mouseenter', popover-title=env.t('gems'), popover=env.t('gemsWhatFor'), popover-placement='bottom',popover-append-to-body='true')
//span.task-action-btn.tile.flush.bright.add-gems-btn
span.task-action-btn.tile.flush.neutral
.Pet_Currency_Gem2x.Gems
| {{user.balance * 4 | number:0}}
li
button.btn.btn-primary(style='margin-top:8px;margin-bottom:0px',ng-if='!user.purchased.plan.customerId',ui-sref='options.settings.subscription',popover-trigger='mouseenter',popover-placement='bottom',popover-title='Subscriptions',popover='Disable ads, buy gems with gold, monthly mystery item, retain progress history, double daily drop-caps, supports the devs. Click for more info.',popover-append-to-body='true') Subscribe
button.btn.btn-default(style='margin-top:8px;margin-bottom:0px',ng-if='user.purchased.plan.customerId',ui-sref='options.settings.subscription',popover-trigger='mouseenter',popover-placement='bottom',popover='Click to manage subscription',popover-append-to-body='true') Subscribed
li(ng-if='user.flags.newStuff')
div.toolbar-bailey-container
|&nbsp;
.npc_bailey.npc_bailey_head(popover=env.t('psst'), popover-trigger='mouseenter', popover-placement='right', ng-click='openModal("newStuff")')
// Invitations
li(ng-if='user.invitations.party.id')
a(ui-sref='options.social.party')
span.glyphicon.glyphicon-user.white(popover='Invited to {{user.invitations.party.name}}',popover-placement='bottom',popover-trigger='mouseenter',popover-append-to-body='true')
li(ng-repeat='guild in user.invitations.guilds')
a(ui-sref='options.social.guilds')
span.glyphicon.glyphicon-user.white(popover='Invited to {{guid.name}}',popover-placement='bottom',popover-trigger='mouseenter',popover-append-to-body='true')
// Chat notifs
li(ng-repeat='(k,v) in user.newMessages', ng-if='v.value')
a(ng-click='k==party._id ? $state.go("options.social.party") : $state.go("options.social.guilds.detail",{gid:k})')
span.glyphicon.glyphicon-comment.white(popover='New message in "{{v.name}}"',popover-placement='bottom',popover-trigger='mouseenter',popover-append-to-body='true')
+16 -15
View File
@@ -1,52 +1,53 @@
// Streak
div(modal='modals.achievements.streak')
script(id='modals/achievements/streak.html', type='text/ng-template')
.modal-header
h3=env.t('modalAchievement')
h4=env.t('modalAchievement')
.modal-body
.achievement.achievement-thermometer
=env.t('streakerAchievement')
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.achievements.streak = false')=env.t('ok')
button.btn.btn-default(ng-click='$close()')=env.t('ok')
// Max Gear
div(modal='modals.achievements.ultimateGear')
script(id='modals/achievements/ultimateGear.html', type='text/ng-template')
.modal-header
h3=env.t('modalAchievement')
h4=env.t('modalAchievement')
.modal-body
.achievement.achievement-armor
=env.t('gearAchievement')
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.achievements.ultimateGear = false')=env.t('ok')
button.btn.btn-default(ng-click='$close()')=env.t('ok')
// Beast Master
div(modal='modals.achievements.beastMaster')
script(id='modals/achievements/beastMaster.html', type='text/ng-template')
.modal-header
h3=env.t('modalAchievement')
h4=env.t('modalAchievement')
.modal-body
p
.achievement.achievement-rat
=env.t('beastAchievement')
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.achievements.beastMaster = false')=env.t('ok')
button.btn.btn-default(ng-click='$close()')=env.t('ok')
// Contributor
div(modal='user.flags.contributor')
// activated by user.flags.contributor
script(id='modals/achievements/contributor.html', type='text/ng-template')
.modal-header
h3=env.t('modalContribAchievement')
h4=env.t('modalContribAchievement')
.modal-body
.npc_justin.float-left
p
!=env.t('contribModal', {name: "{{user.profile.name}}", level: "{{user.contributor.level}}"})
a(href='http://habitrpg.wikia.com/wiki/Contributor_Rewards' target='_blank')=env.t('contribLink')
.modal-footer
button.btn.btn-default.cancel(ng-click='set({"flags.contributor":false})')=env.t('ok')
button.btn.btn-default(ng-click='set({"flags.contributor":false}); $close()')=env.t('ok')
//Rebirth
div(modal='modals.achievements.rebirth')
script(id='modals/achievements/rebirth.html', type='text/ng-template')
.modal-header
h3=env.t('modalAchievement')
h4=env.t('modalAchievement')
.modal-body
.achievement.achievement-sun
=env.t('rebirthAchievement', {number: "{{user.achievements.rebirths}}", level: "{{user.achievements.rebirthLevel}}"})
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.achievements.rebirth = false')=env.t('ok')
button.btn.btn-default(ng-click='$close()')=env.t('ok')
+12 -8
View File
@@ -1,4 +1,5 @@
div(modal='modals.buyGems')
//Why is shown? Because of script here at line 24!!!!!!!!!
script(id='modals/buyGems.html', type='text/ng-template')
.modal-body
.buy-gems
include ../gems
@@ -18,14 +19,17 @@ div(modal='modals.buyGems')
=env.t('USD')
tr
td
.btn.btn-primary(ng-click='showStripe()',style='margin-left:10px;')=env.t('payWithCard')
div(style='float:left;')
//-script(src='/bower_components/JavaScriptButtons/dist/paypal-button.min.js?merchant=#{env.PAYPAL_MERCHANT}', data-button='buynow', data-name=env.t('paypalText'), data-quantity='1', data-amount='5', data-currency='USD', data-tax='0', data-callback='#{env.BASE_URL}/api/v2/user/buy-gems/paypal-ipn', data-env="#{env.NODE_ENV == 'production' ? '' : 'sandbox'}", data-custom='?uid={{user._id}}&apiToken={{user.apiToken}}', data-return='#{env.BASE_URL}', data-rm='1', data-no_shipping='1')
script(src='/bower_components/JavaScriptButtons/dist/paypal-button.min.js?merchant=#{env.PAYPAL_MERCHANT}', data-button='buynow', data-name=env.t('paypalText'), data-quantity='1', data-amount='5', data-currency='USD', data-tax='0', data-callback='#{env.BASE_URL}/api/v2/user/buy-gems/paypal-ipn', data-custom='?uid={{user._id}}&apiToken={{user.apiToken}}', data-return='#{env.BASE_URL}', data-rm='1', data-no_shipping='1')
p
small.muted(style='line-height:10px;')=env.t('payNote')
.row
.col-md-6
.btn.btn-primary(ng-click='showStripe()')=env.t('payWithCard')
.col-md-6
//-script(src='/bower_components/JavaScriptButtons/dist/paypal-button.min.js?merchant=#{env.PAYPAL_MERCHANT}', data-button='buynow', data-name=env.t('paypalText'), data-quantity='1', data-amount='5', data-currency='USD', data-tax='0', data-callback='#{env.BASE_URL}/api/v2/user/buy-gems/paypal-ipn', data-custom='?uid={{user._id}}&apiToken={{user.apiToken}}', data-return='#{env.BASE_URL}', data-rm='1', data-no_shipping='1')
div#custom-paypal-button.paypal-button(ng-init='initPayPalButton()')
.row
.col-md-12
small.muted=env.t('payNote')
div(ng-include="'partials/options.settings.subscription.html'")
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.buyGems = false')=env.t('cancel')
button.btn.btn-default(ng-click='$close()')=env.t('cancel')
+73 -70
View File
@@ -1,75 +1,78 @@
.modal(ng-if='!user.flags.classSelected && user.stats.lvl >= 10', data-backdrop=true, ng-controller='UserCtrl')
script(type='text/ng-template', id='modals/chooseClass.html')
.modal-header
h3=env.t('chooseClass1')
h4
|&nbsp;
=env.t('chooseClass1')
a(href='http://habitrpg.wikia.com/wiki/Class_System' target='_blank')=env.t('chooseClass2')
.modal-body.select-class
.row-fluid
.span3(ng-click='selectedClass = "warrior"')
h5
a(href='http://habitrpg.wikia.com/wiki/Warrior' target='_blank')=env.t('warrior')
figure.herobox(ng-class='{"selected-class": selectedClass=="warrior"}')
.character-sprites
span(class='skin_{{user.preferences.skin}}')
span(class='{{user.preferences.size}}_armor_warrior_5')
span(class='head_0')
span(class='hair_base_{{user.preferences.hair.base}}_{{user.preferences.hair.color}}')
span(class='hair_bangs_{{user.preferences.hair.bangs}}_{{user.preferences.hair.color}}')
span(class='hair_beard_{{user.preferences.hair.beard}}_{{user.preferences.hair.color}}')
span(class='hair_mustache_{{user.preferences.hair.mustache}}_{{user.preferences.hair.color}}')
span(class='head_warrior_5')
span(class='shield_warrior_5')
span(class='weapon_warrior_6')
.span3(ng-click='selectedClass = "wizard"')
h5
a(href='http://habitrpg.wikia.com/wiki/Mage' target='_blank')=env.t('mage')
figure.herobox(ng-class='{"selected-class": selectedClass=="wizard"}')
.character-sprites
span(class='skin_{{user.preferences.skin}}')
span(class='{{user.preferences.size}}_armor_wizard_5')
span(class='head_0')
span(class='hair_base_{{user.preferences.hair.base}}_{{user.preferences.hair.color}}')
span(class='hair_bangs_{{user.preferences.hair.bangs}}_{{user.preferences.hair.color}}')
span(class='hair_beard_{{user.preferences.hair.beard}}_{{user.preferences.hair.color}}')
span(class='hair_mustache_{{user.preferences.hair.mustache}}_{{user.preferences.hair.color}}')
span(class='head_wizard_5')
span(class='shield_wizard_5')
span(class='weapon_wizard_6')
.span3(ng-click='selectedClass = "rogue"')
h5
a(href='http://habitrpg.wikia.com/wiki/Rogue' target='_blank')=env.t('rogue')
figure.herobox(ng-class='{"selected-class": selectedClass=="rogue"}')
.character-sprites
span(class='skin_{{user.preferences.skin}}')
span(class='{{user.preferences.size}}_armor_rogue_5')
span(class='head_0')
span(class='hair_base_{{user.preferences.hair.base}}_{{user.preferences.hair.color}}')
span(class='hair_bangs_{{user.preferences.hair.bangs}}_{{user.preferences.hair.color}}')
span(class='hair_beard_{{user.preferences.hair.beard}}_{{user.preferences.hair.color}}')
span(class='hair_mustache_{{user.preferences.hair.mustache}}_{{user.preferences.hair.color}}')
span(class='head_rogue_5')
span(class='shield_rogue_6')
span(class='weapon_rogue_6')
.span3(ng-click='selectedClass = "healer"')
h5
a(href='http://habitrpg.wikia.com/wiki/Healer' target='_blank')=env.t('healer')
figure.herobox(ng-class='{"selected-class": selectedClass=="healer"}')
.character-sprites
span(class='skin_{{user.preferences.skin}}')
span(class='{{user.preferences.size}}_armor_healer_5')
span(class='head_0')
span(class='hair_base_{{user.preferences.hair.base}}_{{user.preferences.hair.color}}')
span(class='hair_bangs_{{user.preferences.hair.bangs}}_{{user.preferences.hair.color}}')
span(class='hair_beard_{{user.preferences.hair.beard}}_{{user.preferences.hair.color}}')
span(class='hair_mustache_{{user.preferences.hair.mustache}}_{{user.preferences.hair.color}}')
span(class='head_healer_5')
span(class='shield_healer_5')
span(class='weapon_healer_6')
br
.well(ng-show='selectedClass=="warrior"')=env.t('warriorText')
.well(ng-show='selectedClass=="wizard"')=env.t('mageText')
.well(ng-show='selectedClass=="rogue"')=env.t('rogueText')
.well(ng-show='selectedClass=="healer"')=env.t('healerText')
.container-fluid
.row
.col-md-3(ng-click='selectedClass = "warrior"')
h5
a(href='http://habitrpg.wikia.com/wiki/Warrior' target='_blank')=env.t('warrior')
figure.herobox(ng-class='{"selected-class": selectedClass=="warrior"}')
.character-sprites
span(class='skin_{{user.preferences.skin}}')
span(class='{{user.preferences.size}}_armor_warrior_5')
span(class='head_0')
span(class='hair_base_{{user.preferences.hair.base}}_{{user.preferences.hair.color}}')
span(class='hair_bangs_{{user.preferences.hair.bangs}}_{{user.preferences.hair.color}}')
span(class='hair_beard_{{user.preferences.hair.beard}}_{{user.preferences.hair.color}}')
span(class='hair_mustache_{{user.preferences.hair.mustache}}_{{user.preferences.hair.color}}')
span(class='head_warrior_5')
span(class='shield_warrior_5')
span(class='weapon_warrior_6')
.col-md-3(ng-click='selectedClass = "wizard"')
h5
a(href='http://habitrpg.wikia.com/wiki/Mage' target='_blank')=env.t('mage')
figure.herobox(ng-class='{"selected-class": selectedClass=="wizard"}')
.character-sprites
span(class='skin_{{user.preferences.skin}}')
span(class='{{user.preferences.size}}_armor_wizard_5')
span(class='head_0')
span(class='hair_base_{{user.preferences.hair.base}}_{{user.preferences.hair.color}}')
span(class='hair_bangs_{{user.preferences.hair.bangs}}_{{user.preferences.hair.color}}')
span(class='hair_beard_{{user.preferences.hair.beard}}_{{user.preferences.hair.color}}')
span(class='hair_mustache_{{user.preferences.hair.mustache}}_{{user.preferences.hair.color}}')
span(class='head_wizard_5')
span(class='shield_wizard_5')
span(class='weapon_wizard_6')
.col-md-3(ng-click='selectedClass = "rogue"')
h5
a(href='http://habitrpg.wikia.com/wiki/Rogue' target='_blank')=env.t('rogue')
figure.herobox(ng-class='{"selected-class": selectedClass=="rogue"}')
.character-sprites
span(class='skin_{{user.preferences.skin}}')
span(class='{{user.preferences.size}}_armor_rogue_5')
span(class='head_0')
span(class='hair_base_{{user.preferences.hair.base}}_{{user.preferences.hair.color}}')
span(class='hair_bangs_{{user.preferences.hair.bangs}}_{{user.preferences.hair.color}}')
span(class='hair_beard_{{user.preferences.hair.beard}}_{{user.preferences.hair.color}}')
span(class='hair_mustache_{{user.preferences.hair.mustache}}_{{user.preferences.hair.color}}')
span(class='head_rogue_5')
span(class='shield_rogue_6')
span(class='weapon_rogue_6')
.col-md-3(ng-click='selectedClass = "healer"')
h5
a(href='http://habitrpg.wikia.com/wiki/Healer' target='_blank')=env.t('healer')
figure.herobox(ng-class='{"selected-class": selectedClass=="healer"}')
.character-sprites
span(class='skin_{{user.preferences.skin}}')
span(class='{{user.preferences.size}}_armor_healer_5')
span(class='head_0')
span(class='hair_base_{{user.preferences.hair.base}}_{{user.preferences.hair.color}}')
span(class='hair_bangs_{{user.preferences.hair.bangs}}_{{user.preferences.hair.color}}')
span(class='hair_beard_{{user.preferences.hair.beard}}_{{user.preferences.hair.color}}')
span(class='hair_mustache_{{user.preferences.hair.mustache}}_{{user.preferences.hair.color}}')
span(class='head_healer_5')
span(class='shield_healer_5')
span(class='weapon_healer_6')
br
.well(ng-show='selectedClass=="warrior"')=env.t('warriorText')
.well(ng-show='selectedClass=="wizard"')=env.t('mageText')
.well(ng-show='selectedClass=="rogue"')=env.t('rogueText')
.well(ng-show='selectedClass=="healer"')=env.t('healerText')
.modal-footer
button.btn.btn-small.btn-danger.cancel(ng-click='user.ops.disableClasses({})', popover-placement='top', popover-trigger='mouseenter', popover=env.t('optOutText'))=env.t('optOut')
button.btn.btn-primary.cancel(ng-click='selectedClass && changeClass(selectedClass)')=env.t('select')
button.btn.btn-sm.btn-danger(ng-click='user.ops.disableClasses({}); $close()', popover-placement='top', popover-trigger='mouseenter', popover=env.t('optOutText'))=env.t('optOut')
button.btn.btn-primary(ng-disabled='!selectedClass' ng-click='changeClass(selectedClass); $close()')=env.t('select')
+16 -14
View File
@@ -1,14 +1,16 @@
div(modal='user.stats.hp <= 0', options='{backdrop:true, keyboard:false, backdropClick:false}')
.modal-body
.row-fluid
.span4
figure.notification-character
.GrimReaper
.span8
h2=env.t('youDied')
.row-fluid
.span4
p
a.btn.btn-danger.btn-large.notification-action(ng-click='user.ops.revive({})')=env.t('continue')
.span8
p=env.t('dieText')
//div(modal='user.stats.hp <= 0', options='{backdrop:true, keyboard:false, backdropClick:false}')
script(type='text/ng-template', id='modals/death.html')
.modal-body.death-modal
.container-fluid
.row
.col-md-3
figure
.GrimReaper
.col-md-9
h2=env.t('youDied')
.row
.col-md-3
p
a.btn.btn-danger.btn-lg(ng-click='user.ops.revive({}); $close()')=env.t('continue')
.col-md-9
p=env.t('dieText')
+7 -15
View File
@@ -1,23 +1,15 @@
div(modal='modals.dropsEnabled')
script(type='text/ng-template', id='modals/dropsEnabled.html')
.modal-header
h3=env.t('dropsEnabled')
h4=env.t('dropsEnabled')
.modal-body
p
span.item-drop-icon(class='Pet_Egg_Wolf', style='margin-left: 0px')
figure
.item-drop-icon(class='Pet_Egg_Wolf')
span!=env.t('firstDrop', {eggText: "{{Content.eggs.Wolf.text}}", eggNotes: "{{Content.eggs.Wolf.notes}}"})
br
p
span.item-drop-icon(class='Pet_Currency_Gem')
figure
.item-drop-icon(class='Pet_Currency_Gem')
span!=env.t('useGems')
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.dropsEnabled = false')=env.t('close')
div(modal='modals.drop')
.modal-header
h3=env.t('itemDrop')
.modal-body
p
span.item-drop-icon(class='Pet_{{user._tmp.drop.type}}_{{user._tmp.drop.key}}')
| {{user._tmp.drop.dialog}}
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.drop = false')=env.t('close')
button.btn.btn-default(ng-click='$close()')=env.t('close')
+2 -2
View File
@@ -1,5 +1,4 @@
include ./achievements
include ./login
include ./reroll
include ./death
include ./new-stuff
@@ -9,4 +8,5 @@ include ./settings
include ./drops
include ./classes
include ./quests
include ./rebirth
include ./rebirth
include ./limited
+30
View File
@@ -0,0 +1,30 @@
//Valentine
script(id='modals/valentine.html', type='text/ng-template')
.modal-header
h4
.inventory_special_valentine.pull-right
=env.t('valentineCard')
.modal-body
.bg-info(style='padding:10px')
p To: {{user.profile.name}}, From: {{user.items.special.valentineReceived[0]}}
hr
div(ng-switch='Math.floor(Math.random()*3)')
ul.list-unstyled(ng-switch-when='0')
li "Roses are red
li My Dailies are blue
li I'm happy that I'm
li In a Party with you!"
ul.list-unstyled(ng-switch-when='1')
li "Roses are red
li Violets are nice
li Let's get together
li And fight against Vice!"
ul.list-unstyled(ng-switch-default)
li "Roses are red
li This poem style is old
li I hope that you like this
li Cuz it cost ten gold."
p
small For enduring such a saccharine poem, you both receive the "Adoring Friends" badge!
.modal-footer
button.btn.btn-default(ng-click='user.ops.readValentine({});$close()')=env.t('ok')

Some files were not shown because too many files have changed in this diff Show More