mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-08-01 19:50:37 +00:00
Merge remote-tracking branch 'upstream/develop' into manual-due-date
This commit is contained in:
commit
22b429242f
76 changed files with 1544 additions and 966 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -12,4 +12,5 @@ build
|
|||
src/*/*.map
|
||||
src/*/*/*.map
|
||||
test/*.js
|
||||
test/*.map
|
||||
test/*.map
|
||||
public/docs
|
||||
|
|
|
|||
0
.gitmodules
vendored
0
.gitmodules
vendored
7
.travis.yml
Normal file
7
.travis.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- '0.8'
|
||||
- '0.10'
|
||||
before_script:
|
||||
- 'npm install -g bower grunt-cli'
|
||||
- 'bower install'
|
||||
77
DOCS-README.md
Normal file
77
DOCS-README.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# HabitRPG Docs Project
|
||||
|
||||
Generated documentation for all of HabitRPG's source files will be kept in the folder and subfolders. If you would like to use the existing documentation, or contribute to the documentation efforts, read on.
|
||||
|
||||
## Viewing Docs
|
||||
|
||||
You're looking at it!
|
||||
|
||||
Unless you are viewing this file directly from GitHub, you should see a list of files and folders to the left of this readme.
|
||||
|
||||
If you are working locally, you can goto `localhost:3000/docs/` and view the Docs.
|
||||
|
||||
All documentation is generated from comments in the code, into HTML files in the `public/docs/` folder. After you have cloned the HabitRPG repo locally, and done all the `npm install` goodness, the Docs should generate automagickly when you run `grunt run:dev`
|
||||
|
||||
## What I do now?
|
||||
|
||||
Well if you know Markdown, simply add detailed comments in the code using Markdown syntax.
|
||||
|
||||
````
|
||||
/*
|
||||
User.js
|
||||
=======
|
||||
|
||||
Defines the user data model (schema) for use via the API.
|
||||
*/
|
||||
|
||||
// Dependencies
|
||||
// ------------
|
||||
var mongoose = require("mongoose");
|
||||
var Schema = mongoose.Schema;
|
||||
var helpers = require('habitrpg-shared/script/helpers');
|
||||
var _ = require('lodash');....
|
||||
````
|
||||
|
||||
As you can see, you can use both multiline style comments `/* fancy stuff */` and inline comments `// Ooooh my`.
|
||||
|
||||
The exception being end of line comments
|
||||
`text: String, // example: Wolf `
|
||||
|
||||
The above will not be on the "pretty print" side of the Docs, but will stay in the code. An example use case for end of line comments would be for FIXME notes.
|
||||
|
||||
Add anything that would be helpful to a developer regarding how to use the functions, variables, and objects associated with HabitRPG.
|
||||
|
||||
**All documentation should be committed as pull request to the `docs-project` branch of HabitRPG.** Since we are adding comments directly to the code, I don't want to be editing files used for beta or master. We can merge in the docs after we're sure we didn't break anything.
|
||||
|
||||
### jsDoc Syntax
|
||||
|
||||
Yes, the generator also supports jsDoc-style comments such as
|
||||
````
|
||||
@param {Array} files Array of file paths relative to the `inDir` to generate documentation for.
|
||||
````
|
||||
|
||||
**Important Note:** If you use the `@param` syntax, you must use multiline comment blocks (ie `/* stuff */`), otherwise they won't be parsed like parameters.
|
||||
|
||||
This may or may not be useful for HabitRPG. Example use cases:
|
||||
- Documenting the API
|
||||
- Javascript Models
|
||||
|
||||
## Okay, I added great comments. Now what?
|
||||
|
||||
If you're running locally, just re-run `grunt run:dev`. Any changed docs will be automagickly updated.
|
||||
|
||||
Once you're satisfied with the output, push your changes to your fork of HabitRPG and issue a Pull Request on the `docs-project` branch.
|
||||
|
||||
It's that easy!
|
||||
|
||||
## Tech Info
|
||||
|
||||
The generator we are using is [Docker](https://github.com/jbt/docker), which is a fork of [Docco](http://jashkenas.github.io/docco/). Docker supports the same wide-range of filetypes, including being able to generate documentation for a whole project, including an index.
|
||||
|
||||
We also use the [Grunt-Docker](https://github.com/Prevole/grunt-docker) node module for automatic processing.
|
||||
|
||||
## Road Map
|
||||
|
||||
- Customize CSS with HabitRPG specific Styling
|
||||
- Explore possibilities of importing Wiki content
|
||||
- Specify style guide for consistency of comments
|
||||
|
|
@ -4,6 +4,12 @@ module.exports = function(grunt) {
|
|||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
|
||||
karma: {
|
||||
unit: {
|
||||
configFile: 'karma.conf.js'
|
||||
}
|
||||
},
|
||||
|
||||
clean: {
|
||||
build: ['build']
|
||||
},
|
||||
|
|
@ -13,6 +19,7 @@ module.exports = function(grunt) {
|
|||
files: {
|
||||
'build/app.js': [
|
||||
'public/bower_components/jquery/jquery.min.js',
|
||||
'public/bower_components/jquery.cookie/jquery.cookie.js',
|
||||
'public/bower_components/bootstrap-growl/jquery.bootstrap-growl.min.js',
|
||||
'public/bower_components/bootstrap-tour/build/js/bootstrap-tour.min.js',
|
||||
'public/bower_components/angular/angular.min.js',
|
||||
|
|
@ -75,7 +82,6 @@ module.exports = function(grunt) {
|
|||
'public/bower_components/bootstrap/docs/assets/js/bootstrap.min.js',
|
||||
|
||||
'public/js/static.js',
|
||||
'public/js/services/memberServices.js',
|
||||
'public/js/services/userServices.js',
|
||||
'public/js/controllers/authCtrl.js'
|
||||
]
|
||||
|
|
@ -172,5 +178,6 @@ module.exports = function(grunt) {
|
|||
grunt.loadNpmTasks('grunt-concurrent');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-hashres');
|
||||
grunt.loadNpmTasks('grunt-karma');
|
||||
|
||||
};
|
||||
|
|
|
|||
115
README.md
115
README.md
|
|
@ -1,48 +1,27 @@
|
|||
HabitRPG Rewrite
|
||||
HabitRPG
|
||||
===============
|
||||
|
||||
HabitRPG Rewrite under development. Built using Angular, Express, Mongoose, Jade, Stylus, Coffeescript.
|
||||
[HabitRPG](https://habitrpg.com) is an open source habit building program which treats your life like a Role Playing Game. Level up as you succeed, lose HP as you fail, earn money to buy weapons and armor.
|
||||
|
||||
**Note: This branch is under development, and these instructions may fall out of date. They were accurate as of August 5, 2013.** Should you encounter this, join #habitrpg on IRC (Freenode) and talk to litenull.
|
||||
|
||||
Before starting install [MongoDB](http://www.mongodb.org/) and [NodeJS](http://nodejs.org/)
|
||||
|
||||
The general steps are:
|
||||
|
||||
1. Clone the repo
|
||||
1. Install the global dependencies
|
||||
1. Install all dependencies
|
||||
1. Run the client
|
||||
|
||||
Or, expressed in commands on the command line:
|
||||
|
||||
1. `git clone --recursive -b develop https://github.com/lefnire/habitrpg.git`
|
||||
1. 'npm install -g grunt-cli' (you may need to add `sudo` in front of it)
|
||||
1. `cd habitrpg && npm install`
|
||||
1. `grunt run:dev`
|
||||
|
||||
To access the site, open http://localhost:3000 in your browser.
|
||||
|
||||
There are a few other Grunt task avalaible:
|
||||
|
||||
- `grunt build:dev` - Compile, concat and minify Stylus files
|
||||
- `grunt build:prod` - Same as `grunt build:dev` but concat and minify Javascript files.
|
||||
- `grunt nodemon` - Start the server with **nodemon**, restart when a file change but without compiling Stylus files
|
||||
|
||||
# Technologies
|
||||
|
||||
1. Angular, Express, Mongoose. Awesome, tried technologies. Read up on them.
|
||||
1. CoffeeScript, Stylus, Jade - big debate.
|
||||
1. Jade. We need a server-side templating language so we can inject variables (`res.locals` from Express). Jade is great
|
||||
because the "significant whitespace" paradigm protects you from HTML errors such as missing or mal-matched close tags,
|
||||
which has been a pretty common error from multiple contribs on Habit. However, it's not very HTML-y, and makes people mad.
|
||||
We'll re-visit this conversation after the rewrite is done.
|
||||
1. Stylus. We're either staying here or moving to LESS, but vanilla CSS isn't cutting it for our app.
|
||||
1. CoffeeScript. This is the hottest debate. I'm using it to rewrite, and Habit was written originally on CS. It's a
|
||||
fantastic language, but it's a barrier-to-entry for potential contribs who don't know it. Will also revisit right after
|
||||
the rewrite.
|
||||
Built using Angular, Express, Mongoose, Jade, Stylus, Grunt and Bower.
|
||||
|
||||
# Windows Environment Install
|
||||
# Set up HabitRPG locally
|
||||
|
||||
**Windows** users should skip this section and read the one below with Windows-specific steps.
|
||||
|
||||
Before starting make sure to have [MongoDB](http://www.mongodb.org/), [NodeJS and npm](http://nodejs.org/) and [Git](https://help.github.com/articles/set-up-git) installed and set up.
|
||||
|
||||
1. [Fork the repository](https://help.github.com/articles/fork-a-repo) on your computer.
|
||||
1. Checkout the **develop** branch where all the development happens:
|
||||
`git checkout -b develop origin/develop`
|
||||
1. Install **grunt-cli** npm package globally (on some systems you may need to add `sudo` in front of the command below):
|
||||
`npm install -g grunt-cli bower`
|
||||
1. Install the **npm** and **bower** packages:
|
||||
`npm install`
|
||||
1. Create a config file from the example one:
|
||||
`cp config.json.example config.json`
|
||||
|
||||
## Windows Environment Install
|
||||
|
||||
1. Set up MongoDB. Steps:
|
||||
1. Download the latest production release of MongoDB from: http://www.mongodb.org/downloads
|
||||
|
|
@ -50,27 +29,22 @@ There are a few other Grunt task avalaible:
|
|||
1. Rename the folder from mongodb-win32-x86_64-2.4.6 to mongodb
|
||||
1. Create a data\db directory under the application directory. Example: c:\apps\mongodb\data\db
|
||||
1. Start up MongoDB using the following command:
|
||||
'c:\apps\mongodb\bin\mongod.exe --dbpath c:\apps\mongodb\data'
|
||||
'c:\apps\mongodb\bin\mongod.exe --dbpath c:\apps\mongodb\data'
|
||||
|
||||
If MongoDB starts up successfully, you should see the following at the end of the logs:
|
||||
Sun Sep 01 18:10:21.233 [initandlisten] waiting for connections on port 27017
|
||||
```Sun Sep 01 18:10:21.233 [initandlisten] waiting for connections on port 27017
|
||||
Sun Sep 01 18:10:21.233 [websvr] admin web console waiting for connections on po
|
||||
rt 28017
|
||||
|
||||
rt 28017```
|
||||
|
||||
1. Install Node.js (includes npm). Steps:
|
||||
1. Download and run the latest Node.js msi installation file from http://nodejs.org/download/
|
||||
1. Create a fork of the habitrpg repository on github under your own account
|
||||
1. Install Git and download angular_rewrite code repository. Steps:
|
||||
1. Install latest stable version of Git, found here: http://git-scm.com/downloads
|
||||
1. Make sure to select "Run Git from the Windows Command Prompt" during the installation process
|
||||
1. Open a command window. Navigate to the location where you would like the project files to live. Example: c:\projects
|
||||
1. Run git command to download angular_rewrite branch.
|
||||
'git clone --recursive -b develop https://github.com/ezinaz/habitrpg.git' (where 'ezinaz' is your account name)
|
||||
1. Run 'cd habitrpg'
|
||||
1. Create upstream remote:
|
||||
'git remote add upstream https://github.com/lefnire/habitrpg.git'
|
||||
1. Run 'git fetch upstream'
|
||||
1. Run 'npm install'. Read below for possible error message.
|
||||
1. Install [Git](https://help.github.com/articles/set-up-git).
|
||||
1. [Fork the repository](https://help.github.com/articles/fork-a-repo) on your computer.
|
||||
1. Checkout the **develop** branch where all the development happens:
|
||||
`git checkout -b develop origin/develop`
|
||||
1. Install the **npm** packages:
|
||||
`npm install`
|
||||
Read below for possible error message.
|
||||
|
||||
You might receive the following error during the 'npm install' command:
|
||||
> habitrpg@0.0.0-152 postinstall C:\Users\022498\Projects\habitrpg
|
||||
|
|
@ -82,18 +56,29 @@ npm ERR! not ok code 0
|
|||
|
||||
Ignore this error and proceed with the following:
|
||||
|
||||
1. Run 'npm install -g grunt-cli'
|
||||
1. Run 'npm install -g bower'
|
||||
1. Run 'bower install -f'
|
||||
1. Run 'copy config.json.example config.json'
|
||||
1. `grunt run:dev`
|
||||
|
||||
Open a browser to URL http://localhost:3000 to test the application.
|
||||
|
||||
1. Install **grunt-cli** and **bower** npm packages globally
|
||||
'npm install -g grunt-cli bower'
|
||||
1. Install the **bower** packages:
|
||||
'bower install -f'
|
||||
1. Create a config file from the example one:
|
||||
`copy config.json.example config.json`
|
||||
|
||||
# Run HabitRPG
|
||||
|
||||
HabitRPG uses [Grunt](http://gruntjs.com) as its build tool.
|
||||
|
||||
`grunt run:dev` compiles the **Stylus** files and start a web server.
|
||||
|
||||
It uses [Nodemon](https://github.com/remy/nodemon) and [grunt-contrib-watch](https://github.com/gruntjs/grunt-contrib-watch) to automatically restart the server and re-compile the files when a change is detected.
|
||||
|
||||
**Open a browser to URL http://localhost:3000 to test the application!**
|
||||
|
||||
# Technologies discussion
|
||||
|
||||
1. Angular, Express, Mongoose. Awesome, tried technologies. Read up on them.
|
||||
1. Stylus, Jade - big debate.
|
||||
1. Jade. We need a server-side templating language so we can inject variables (`res.locals` from Express). Jade is great
|
||||
because the "significant whitespace" paradigm protects you from HTML errors such as missing or mal-matched close tags,
|
||||
which has been a pretty common error from multiple contribs on Habit. However, it's not very HTML-y, and makes people mad.
|
||||
We'll re-visit this conversation after the rewrite is done.
|
||||
1. Stylus. We're either staying here or moving to LESS, but vanilla CSS isn't cutting it for our app.
|
||||
|
|
|
|||
|
|
@ -10,31 +10,6 @@ misc = require './misc'
|
|||
_.each completedIds, (id) -> user.del "tasks.#{id}"; true
|
||||
user.set 'todoIds', _.difference(todoIds, completedIds)
|
||||
|
||||
appExports.toggleChart = (e, el) ->
|
||||
id = $(el).attr('data-id')
|
||||
[historyPath, togglePath] = ['','']
|
||||
|
||||
switch id
|
||||
when 'exp'
|
||||
[togglePath, historyPath] = ['_page.charts.exp', '_user.history.exp']
|
||||
when 'todos'
|
||||
[togglePath, historyPath] = ['_page.charts.todos', '_user.history.todos']
|
||||
else
|
||||
[togglePath, historyPath] = ["_page.charts.#{id}", "_user.tasks.#{id}.history"]
|
||||
model.set "_tasks.editing.#{id}", false
|
||||
|
||||
history = model.get(historyPath)
|
||||
model.set togglePath, !(model.get togglePath)
|
||||
|
||||
matrix = [['Date', 'Score']]
|
||||
_.each history, (obj) -> matrix.push([ moment(obj.date).format('MM/DD/YY'), obj.value ])
|
||||
data = google.visualization.arrayToDataTable matrix
|
||||
options =
|
||||
title: 'History'
|
||||
backgroundColor: { fill:'transparent' }
|
||||
chart = new google.visualization.LineChart $(".#{id}-chart")[0]
|
||||
chart.draw(data, options)
|
||||
|
||||
|
||||
###
|
||||
Undo
|
||||
|
|
|
|||
18
bower.json
18
bower.json
|
|
@ -15,8 +15,8 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"jquery": "~2.0.3",
|
||||
"angular": "1.2.0-rc.1",
|
||||
"angular-resource": "1.2.0-rc.1",
|
||||
"angular": "1.2.0-rc.2",
|
||||
"angular-resource": "1.2.0-rc.2",
|
||||
"angular-ui": "~0.4.0",
|
||||
"angular-bootstrap": "~0.5.0",
|
||||
"habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared.git#rewrite",
|
||||
|
|
@ -29,12 +29,18 @@
|
|||
"sticky": "*",
|
||||
"bootstrap-datepicker": "~1.2.0",
|
||||
"bootstrap": "v2.3.2",
|
||||
"angular-route": "1.2.0-rc.1",
|
||||
"angular-route": "1.2.0-rc.2",
|
||||
"angular-ui-utils": "~0.0.4",
|
||||
"angular-sanitize": "1.2.0-rc.1",
|
||||
"marked": "~0.2.9"
|
||||
"angular-sanitize": "1.2.0-rc.2",
|
||||
"marked": "~0.2.9",
|
||||
"JavaScriptButtons": "git://github.com/paypal/JavaScriptButtons.git#master"
|
||||
},
|
||||
"resolutions": {
|
||||
"jquery": "~2.0.3"
|
||||
"jquery": "~2.0.3",
|
||||
"bootstrap": "v2.3.2",
|
||||
"angular": "1.2.0-rc.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "1.2.0-rc.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,5 +12,6 @@
|
|||
"SMTP_PASS":"password",
|
||||
"SMTP_SERVICE":"Gmail",
|
||||
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY":"22223333444455556666777788889999"
|
||||
}
|
||||
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
|
||||
"PAYPAL_MERCHANT":"paypal-merchant@gmail.com"
|
||||
}
|
||||
|
|
|
|||
54
karma-e2e.conf.js
Normal file
54
karma-e2e.conf.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Karma configuration
|
||||
// http://karma-runner.github.io/0.10/config/configuration-file.html
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
// base path, that will be used to resolve files and exclude
|
||||
basePath: '',
|
||||
|
||||
// testing framework to use (jasmine/mocha/qunit/...)
|
||||
frameworks: ['ng-scenario'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'test/e2e/**/*.js'
|
||||
],
|
||||
|
||||
// list of files / patterns to exclude
|
||||
exclude: [],
|
||||
|
||||
// web server port
|
||||
port: 8080,
|
||||
|
||||
// level of logging
|
||||
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: false,
|
||||
|
||||
|
||||
// Start these browsers, currently available:
|
||||
// - Chrome
|
||||
// - ChromeCanary
|
||||
// - Firefox
|
||||
// - Opera
|
||||
// - Safari (only Mac)
|
||||
// - PhantomJS
|
||||
// - IE (only Windows)
|
||||
browsers: ['Chrome'],
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, it capture browsers, run tests and exit
|
||||
singleRun: false
|
||||
|
||||
// Uncomment the following lines if you are using grunt's server to run the tests
|
||||
// proxies: {
|
||||
// '/': 'http://localhost:9000/'
|
||||
// },
|
||||
// URL root prevent conflicts with the site root
|
||||
// urlRoot: '_karma_'
|
||||
});
|
||||
};
|
||||
56
karma.conf.js
Normal file
56
karma.conf.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// Karma configuration
|
||||
// http://karma-runner.github.io/0.10/config/configuration-file.html
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
// base path, that will be used to resolve files and exclude
|
||||
basePath: '',
|
||||
|
||||
// testing framework to use (jasmine/mocha/qunit/...)
|
||||
frameworks: ['mocha', 'chai', 'chai-as-promised', 'sinon-chai'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'public/bower_components/angular/angular.js',
|
||||
'public/bower_components/angular-resource/angular-resource.min.js',
|
||||
'public/bower_components/angular-mocks/angular-mocks.js',
|
||||
'public/bower_components/marked/lib/marked.js',
|
||||
'public/bower_components/habitrpg-shared/dist/habitrpg-shared.js',
|
||||
'public/js/*.js',
|
||||
'public/js/**/*.js',
|
||||
'test/mock/**/*.js',
|
||||
'test/spec/*.js',
|
||||
'test/spec/**/*.js'
|
||||
],
|
||||
|
||||
// list of files / patterns to exclude
|
||||
exclude: [],
|
||||
|
||||
// web server port
|
||||
port: 8080,
|
||||
|
||||
// level of logging
|
||||
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: false,
|
||||
|
||||
|
||||
// Start these browsers, currently available:
|
||||
// - Chrome
|
||||
// - ChromeCanary
|
||||
// - Firefox
|
||||
// - Opera
|
||||
// - Safari (only Mac)
|
||||
// - PhantomJS
|
||||
// - IE (only Windows)
|
||||
browsers: ['Chrome'],
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, it capture browsers, run tests and exit
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
5
migrations/20131022_purchased_and_newStuff.js
Normal file
5
migrations/20131022_purchased_and_newStuff.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
db.users.find().forEach(function(user){
|
||||
if (!user.purchased) user.purchased = {hair: {}, skin: {}};
|
||||
user.purchased.ads = user.flags && !!user.flags.ads;
|
||||
db.users.update({_id:user._id}, {$set:{'purchased': user.purchased, 'flags.newStuff': true}, $unset: {'flags.ads':1}});
|
||||
});
|
||||
12
migrations/20131022_restore_ads.js
Normal file
12
migrations/20131022_restore_ads.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// node .migrations/20131022_restore_ads.js
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
var dbBackup = mongo.db('localhost:27017/habitrpg?auto_reconnect');
|
||||
var dbLive = mongo.db('localhost:27017/habitrpg2?auto_reconnect');
|
||||
var count = 89474;
|
||||
dbBackup.collection('users').findEach({$or: [{'flags.ads':'show'}, {'flags.ads': null}]}, {batchSize:10}, function(err, item) {
|
||||
if (err) return console.error({err:err});
|
||||
if (!item || !item._id) return console.error('blank user');
|
||||
dbLive.collection('users').update({_id:item._id}, {$set:{'purchased.ads':false}, $unset: {'flags.ads': 1}});
|
||||
if (--count <= 0) console.log("DONE!");
|
||||
});
|
||||
|
|
@ -1 +1 @@
|
|||
db.users.update({},{$set:{'flags.newStuff':'show'}},{multi:true})
|
||||
db.users.update({},{$set:{'flags.newStuff':true}},{multi:true})
|
||||
20
package.json
20
package.json
|
|
@ -41,7 +41,8 @@
|
|||
"passport": "~0.1.17",
|
||||
"validator": "~1.5.1",
|
||||
"nodemailer": "~0.5.2",
|
||||
"grunt-cli": "~0.1.9"
|
||||
"grunt-cli": "~0.1.9",
|
||||
"paypal-ipn": "~1.0.1"
|
||||
},
|
||||
"private": true,
|
||||
"subdomain": "habitrpg",
|
||||
|
|
@ -57,5 +58,22 @@
|
|||
"test": "mocha test/api.mocha.coffee",
|
||||
"start": "grunt run:dev",
|
||||
"postinstall": "./node_modules/bower/bin/bower install -f"
|
||||
},
|
||||
"devDependencies": {
|
||||
"karma-ng-scenario": "~0.1.0",
|
||||
"grunt-karma": "~0.6.2",
|
||||
"karma-script-launcher": "~0.1.0",
|
||||
"karma-chrome-launcher": "~0.1.0",
|
||||
"karma-firefox-launcher": "~0.1.0",
|
||||
"karma-html2js-preprocessor": "~0.1.0",
|
||||
"karma-coffee-preprocessor": "~0.1.0",
|
||||
"karma-jasmine": "~0.1.3",
|
||||
"karma-requirejs": "~0.1.0",
|
||||
"karma-phantomjs-launcher": "~0.1.0",
|
||||
"karma": "~0.10.2",
|
||||
"karma-ng-html2js-preprocessor": "~0.1.0",
|
||||
"karma-chai-plugins": "~0.1.0",
|
||||
"mocha": "~1.12.1",
|
||||
"karma-mocha": "~0.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,70 +52,6 @@ menu
|
|||
}
|
||||
}
|
||||
|
||||
// narrower spriting for customize modal
|
||||
.customize-menu
|
||||
.m_hair_blond,.m_hair_black,.m_hair_brown,.m_hair_white,
|
||||
.f_hair_blond,.f_hair_black,.f_hair_brown,.f_hair_white,
|
||||
.m_skin_dead,.m_skin_orc,.m_skin_asian,.m_skin_black,.m_skin_white,
|
||||
.f_skin_dead,.f_skin_orc,.f_skin_asian,.f_skin_black,.f_skin_white,
|
||||
.m_head_0,.f_head_0
|
||||
width: 60px
|
||||
height: 60px
|
||||
|
||||
// head
|
||||
.m_head_0
|
||||
background-position: -1557px -9px;
|
||||
|
||||
// hair
|
||||
.m_hair_blond
|
||||
background-position: -1647px -4px;
|
||||
.m_hair_black
|
||||
background-position: -1737px -4px;
|
||||
.m_hair_brown
|
||||
background-position: -1827px -4px;
|
||||
.m_hair_white
|
||||
background-position: -1917px -4px;
|
||||
|
||||
// skin
|
||||
.m_skin_dead
|
||||
background-position: -2547px -20px;
|
||||
.m_skin_orc
|
||||
background-position: -2637px -20px;
|
||||
.m_skin_asian
|
||||
background-position: -2727px -20px;
|
||||
.m_skin_black
|
||||
background-position: -2817px -20px;
|
||||
.m_skin_white
|
||||
background-position: -2907px -20px;
|
||||
|
||||
// head
|
||||
.f_head_0
|
||||
background-position: -1917px -9px;
|
||||
|
||||
// hair
|
||||
.f_hair_white
|
||||
background-position: -2009px -8px;
|
||||
.f_hair_brown
|
||||
background-position: -2099px -8px;
|
||||
.f_hair_black
|
||||
background-position: -2189px -8px;
|
||||
.f_hair_blond
|
||||
background-position: -2279px -8px;
|
||||
|
||||
// skin
|
||||
.f_skin_dead
|
||||
background-position: -2997px -20px;
|
||||
.f_skin_orc
|
||||
background-position: -3087px -20px;
|
||||
.f_skin_asian
|
||||
background-position: -3177px -20px;
|
||||
.f_skin_black
|
||||
background-position: -3267px -20px;
|
||||
.f_skin_white
|
||||
background-position: -3357px -20px;
|
||||
|
||||
// starting armor
|
||||
.f_armor_0_v2
|
||||
background-position: -2819px -38px;
|
||||
.f_armor_0_v1
|
||||
background-position: -2909px -38px;
|
||||
.well.limited-edition
|
||||
padding: 5px
|
||||
margin: 0px
|
||||
|
|
@ -7,8 +7,9 @@
|
|||
background: #f5f5f5
|
||||
border-bottom: 1px solid rgba(0,0,0,0.2)
|
||||
// margin-top: -1px
|
||||
overflow: hidden
|
||||
position relative
|
||||
overflow-y: hidden
|
||||
overflow-x: auto
|
||||
// position relative
|
||||
|
||||
/* login/menu buttons
|
||||
--------------------- */
|
||||
|
|
@ -17,7 +18,7 @@
|
|||
top: 0.5em
|
||||
right: 0.5em
|
||||
font-size: 0.85em
|
||||
z-index: 9000
|
||||
z-index: 1011
|
||||
|
||||
.site-nav
|
||||
margin-bottom: 0px
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ hr
|
|||
position: fixed;
|
||||
top: 0;
|
||||
right: 0px;
|
||||
z-index: 1001;
|
||||
z-index: 1061;
|
||||
|
||||
.modal-body
|
||||
margin: 10px
|
||||
|
|
@ -168,4 +168,8 @@ a
|
|||
word-wrap: break-word
|
||||
|
||||
.btn
|
||||
margin-right: 5px
|
||||
margin-right: 5px
|
||||
|
||||
.modal-indented-list
|
||||
margin-left: 10px;
|
||||
padding-left: 10px;
|
||||
|
|
@ -12,6 +12,9 @@
|
|||
.tile
|
||||
opacity: 1
|
||||
|
||||
.modal-header .gem-wallet
|
||||
padding-top: 0px
|
||||
|
||||
|
||||
// pets (this will all change when pet system is overhauled)
|
||||
.pet-grid
|
||||
|
|
@ -36,6 +39,10 @@
|
|||
width:6.5em
|
||||
height:2.5em
|
||||
|
||||
menu.pets div
|
||||
display: inline-block
|
||||
padding-top: -30px
|
||||
|
||||
.current-pet
|
||||
left: 0px
|
||||
bottom: 0px
|
||||
|
|
@ -96,3 +103,10 @@
|
|||
-moz-filter: brightness(0%)
|
||||
-o-filter: brightness(0%)
|
||||
-ms-filter: brightness(0%)*/
|
||||
|
||||
.selectableInventory
|
||||
background-color: lightgreen !important
|
||||
|
||||
.sell-inventory
|
||||
width: 162px
|
||||
height: 138px
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
.NPC-Alex, .NPC-Bailey, .NPC-Bailey-Head, .NPC-Daniel, .NPC-Justin, .NPC-Justin-Head, .NPC-Matt {background: url("/bower_components/habitrpg-shared/img/npcs/NPC-SpriteSheet.png") no-repeat}
|
||||
.NPC-Alex, .NPC-Bailey, .NPC-Bailey-Head, .NPC-Daniel, .NPC-Justin, .NPC-Justin-Head, .NPC-Matt {background: url("/bower_components/habitrpg-shared/img/npcs/halloween/NPC-SpriteSheet.png") no-repeat}
|
||||
|
||||
.NPC-Alex {background-position: 0 0; width: 162px; height: 138px;}
|
||||
.NPC-Alex-container{margin-bottom: 20px;}
|
||||
|
|
@ -18,17 +18,19 @@
|
|||
width: 54px
|
||||
height: 30px
|
||||
position: absolute
|
||||
bottom: 0px
|
||||
top: 117px // 147 (header-height) - 30 (bailey height)
|
||||
right: 50px
|
||||
cursor: pointer
|
||||
|
||||
|
||||
.static-popover
|
||||
z-index 0
|
||||
display block
|
||||
position:relative
|
||||
|
||||
// Tour (Justin)
|
||||
.tour-tour .NPC-Justin
|
||||
.NPC-Justin.float-left
|
||||
float: left
|
||||
margin-right: 5px
|
||||
margin-bottom: 5px
|
||||
.popover-navigation {clear:both;}
|
||||
|
|
@ -4,8 +4,8 @@
|
|||
The authentication controller (login & facebook)
|
||||
*/
|
||||
|
||||
habitrpg.controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', 'API_URL',
|
||||
function($scope, $rootScope, User, $http, $location, API_URL) {
|
||||
habitrpg.controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','API_URL',
|
||||
function($scope, $rootScope, User, $http, $location, $window, API_URL) {
|
||||
var runAuth;
|
||||
var showedFacebookMessage;
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ habitrpg.controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$loca
|
|||
|
||||
runAuth = function(id, token) {
|
||||
User.authenticate(id, token, function(err) {
|
||||
window.location.href = '/';
|
||||
$window.location.href = '/';
|
||||
//$rootScope.modals.login = false;
|
||||
});
|
||||
};
|
||||
|
|
@ -41,22 +41,22 @@ habitrpg.controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$loca
|
|||
runAuth(data.id, data.apiToken);
|
||||
}).error(function(data, status, headers, config) {
|
||||
if (status === 0) {
|
||||
alert("Server not currently reachable, try again later");
|
||||
$window.alert("Server not currently reachable, try again later");
|
||||
} else if (!!data && !!data.err) {
|
||||
alert(data.err);
|
||||
$window.alert(data.err);
|
||||
} else {
|
||||
alert("ERROR: " + status);
|
||||
$window.alert("ERROR: " + status);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function errorAlert(data, status, headers, config) {
|
||||
if (status === 0) {
|
||||
alert("Server not currently reachable, try again later");
|
||||
$window.alert("Server not currently reachable, try again later");
|
||||
} else if (!!data && !!data.err) {
|
||||
alert(data.err);
|
||||
$window.alert(data.err);
|
||||
} else {
|
||||
alert("ERROR: " + status);
|
||||
$window.alert("ERROR: " + status);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ habitrpg.controller("FooterCtrl", ['$scope', '$rootScope', 'User', '$http',
|
|||
$.getScript('//checkout.stripe.com/v2/checkout.js');
|
||||
|
||||
// Amazon Affiliate
|
||||
// if ($rootScope.authenticated() && User.user.flags.ads !== 'hide') {
|
||||
// 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="" />');
|
||||
// });
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
|
|||
}
|
||||
|
||||
$scope.deleteChatMessage = function(group, message){
|
||||
if(message.uuid === User.user.id){
|
||||
if(message.uuid === User.user.id || (User.user.backer && User.user.backer.admin)){
|
||||
group.$deleteChatMessage({messageId: message.id}, function(){
|
||||
var i = _.indexOf(group.chat, message);
|
||||
if(i !== -1) group.chat.splice(i, 1);
|
||||
|
|
@ -89,6 +89,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
|
|||
}
|
||||
|
||||
$scope.nameTagClasses = function(message){
|
||||
if (!message) return; // fixme what's triggering this?
|
||||
if (message.contributor) {
|
||||
if (message.contributor.match(/npc/i) || message.contributor.match(/royal/i)) {
|
||||
return 'label-royal';
|
||||
|
|
@ -113,7 +114,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
|
|||
|
||||
$scope.create = function(group){
|
||||
if (User.user.balance < 1) {
|
||||
return $rootScope.modals.moreGems = true;
|
||||
return $rootScope.modals.buyGems = true;
|
||||
// $('#more-gems-modal').modal('show');
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +143,9 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
|
|||
}
|
||||
|
||||
$scope.leave = function(group){
|
||||
if (confirm("Are you sure you want to leave this guild?") !== true) {
|
||||
return;
|
||||
}
|
||||
group.$leave();
|
||||
// var i = _.find($scope.groups.guilds, {_id:group._id});
|
||||
// if (~i) $scope.groups.guilds.splice(i, 1);
|
||||
|
|
@ -178,6 +182,9 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
|
|||
});
|
||||
}
|
||||
$scope.leave = function(group){
|
||||
if (confirm("Are you sure you want to leave this party?") !== true) {
|
||||
return;
|
||||
}
|
||||
group.$leave(function(){
|
||||
Groups.groups.party = new Groups.Group();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,32 +1,78 @@
|
|||
habitrpg.controller("InventoryCtrl", ['$scope', 'User',
|
||||
function($scope, User) {
|
||||
|
||||
$scope.hatching = false;
|
||||
// convenience vars since these are accessed frequently
|
||||
$scope.userEggs = User.user.items.eggs;
|
||||
$scope.userHatchingPotions = User.user.items.hatchingPotions;
|
||||
|
||||
$scope.chooseEgg = function(egg){
|
||||
if($scope.userHatchingPotions && $scope.userHatchingPotions.length < 1) {
|
||||
return alert("You have no hatching potion!");
|
||||
}
|
||||
$scope.selectedEgg = null; // {index: 1, name: "Tiger", value: 5}
|
||||
$scope.selectedPotion = null; // {index: 5, name: "Red", value: 3}
|
||||
|
||||
$scope.selectedEgg = egg;
|
||||
$scope.selectedPotion = $scope.userHatchingPotions[0];
|
||||
$scope.hatching = true;
|
||||
$scope.chooseEgg = function(egg, $index){
|
||||
if ($scope.selectedEgg && $scope.selectedEgg.index == $index) {
|
||||
return $scope.selectedEgg = null; // clicked same egg, unselect
|
||||
}
|
||||
var eggData = _.defaults({index:$index}, egg);
|
||||
if (!$scope.selectedPotion) {
|
||||
$scope.selectedEgg = eggData;
|
||||
} else {
|
||||
$scope.hatch(eggData, $scope.selectedPotion);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.pour = function(){
|
||||
var pet = $scope.selectedEgg.name + '-' + $scope.selectedPotion;
|
||||
$scope.choosePotion = function(potion, $index){
|
||||
if ($scope.selectedPotion && $scope.selectedPotion.index == $index) {
|
||||
return $scope.selectedPotion = null; // clicked same egg, unselect
|
||||
}
|
||||
// we really didn't think through the way these things are stored and getting passed around...
|
||||
var potionData = _.findWhere(window.habitrpgShared.items.items.hatchingPotions, {name:potion});
|
||||
potionData = _.defaults({index:$index}, potionData);
|
||||
if (!$scope.selectedEgg) {
|
||||
$scope.selectedPotion = potionData;
|
||||
} else {
|
||||
$scope.hatch($scope.selectedEgg, potionData);
|
||||
}
|
||||
}
|
||||
|
||||
if(User.user.items.pets && ~User.user.items.pets.indexOf(pet)) {
|
||||
$scope.sellInventory = function() {
|
||||
if ($scope.selectedEgg) {
|
||||
$scope.userEggs.splice($scope.selectedEgg.index, 1);
|
||||
User.setMultiple({
|
||||
'items.eggs': $scope.userEggs,
|
||||
'stats.gp': User.user.stats.gp + $scope.selectedEgg.value
|
||||
});
|
||||
$scope.selectedEgg = null;
|
||||
} else if ($scope.selectedPotion) {
|
||||
$scope.userHatchingPotions.splice($scope.selectedPotion.index, 1);
|
||||
User.setMultiple({
|
||||
'items.hatchingPotions': $scope.userHatchingPotions,
|
||||
'stats.gp': User.user.stats.gp + $scope.selectedPotion.value
|
||||
});
|
||||
$scope.selectedPotion = null;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.ownsPet = function(egg, potion){
|
||||
if (!egg || !potion) return;
|
||||
var pet = egg.name + '-' + potion;
|
||||
return User.user.items.pets && ~User.user.items.pets.indexOf(pet)
|
||||
}
|
||||
|
||||
$scope.selectableInventory = function(egg, potion, $index) {
|
||||
if (!egg || !potion) return;
|
||||
// FIXME this isn't updating the view for some reason
|
||||
//if ($scope.selectedEgg && $scope.selectedEgg.index == $index) return 'selectableInventory';
|
||||
//if ($scope.selectedPotion && $scope.selectedPotion.index == $index) return 'selectableInventory';
|
||||
if (!$scope.ownsPet(egg, potion)) return 'selectableInventory';
|
||||
}
|
||||
|
||||
$scope.hatch = function(egg, potion){
|
||||
if ($scope.ownsPet(egg, potion.name)){
|
||||
return alert("You already have that pet, hatch a different combo.")
|
||||
}
|
||||
|
||||
var i = _.indexOf($scope.userEggs, $scope.selectedEgg);
|
||||
$scope.userEggs.splice(i, 1);
|
||||
|
||||
i = _.indexOf($scope.userHatchingPotions, $scope.selectedPotion);
|
||||
$scope.userHatchingPotions.splice(i, 1);
|
||||
var pet = egg.name + '-' + potion.name;
|
||||
$scope.userEggs.splice(egg.index, 1);
|
||||
$scope.userHatchingPotions.splice(potion.index, 1);
|
||||
|
||||
if(!User.user.items.pets) User.user.items.pets = [];
|
||||
User.user.items.pets.push(pet);
|
||||
|
|
@ -40,7 +86,7 @@ habitrpg.controller("InventoryCtrl", ['$scope', 'User',
|
|||
alert("Your egg hatched! Visit your stable to equip your pet.");
|
||||
|
||||
$scope.selectedEgg = null;
|
||||
$scope.hatching = false;
|
||||
$scope.selectedPotion = null;
|
||||
}
|
||||
|
||||
}]);
|
||||
|
|
@ -12,7 +12,7 @@ habitrpg.controller("MarketCtrl", ['$rootScope', '$scope', 'User', 'API_URL', '$
|
|||
storePath = type === 'egg' ? 'items.eggs' : 'items.hatchingPotions'
|
||||
|
||||
if(gems < item.value){
|
||||
return $rootScope.modals.moreGems = true;
|
||||
return $rootScope.modals.buyGems = true;
|
||||
}
|
||||
|
||||
var message = "Buy this " + (type == 'egg' ? 'egg' : 'hatching potion') + " with " + item.value + " of your " + gems + " Gems?"
|
||||
|
|
|
|||
|
|
@ -16,15 +16,22 @@ habitrpg.controller('MenuCtrl',
|
|||
}
|
||||
|
||||
$scope.gotoOptions = function(){
|
||||
$scope.viewingOptions = true;
|
||||
$location.path('/options');
|
||||
}
|
||||
|
||||
$scope.gotoTasks = function(){
|
||||
$scope.viewingOptions = false;
|
||||
$location.path('/tasks')
|
||||
}
|
||||
|
||||
$scope.$on('$routeChangeSuccess', function(ev, current) {
|
||||
if(!current.$$route) return;
|
||||
if(current.$$route.originalPath === "/tasks"){
|
||||
$scope.viewingOptions = false;
|
||||
}else if(current.$$route.originalPath === "/options"){
|
||||
$scope.viewingOptions = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//FIXME where to implement this in rewrite?
|
||||
|
||||
|
|
|
|||
|
|
@ -1,120 +1,60 @@
|
|||
'use strict';
|
||||
|
||||
habitrpg.controller('NotificationCtrl',
|
||||
['$scope', '$rootScope', 'User', 'Guide', function ($scope, $rootScope, User, Guide) {
|
||||
['$scope', '$rootScope', 'User', 'Guide', 'Notification', function ($scope, $rootScope, User, Guide, Notification) {
|
||||
|
||||
Guide.initTour();
|
||||
$rootScope.$watch('user.stats.hp', function(after, before) {
|
||||
if (after == before) return;
|
||||
Notification.hp(after - before, 'hp');
|
||||
});
|
||||
|
||||
function growlNotification(html, type) {
|
||||
$.bootstrapGrowl(html, {
|
||||
ele: '#notification-area',
|
||||
type: type, //(null, 'info', 'error', 'success', 'gp', 'xp', 'hp', 'lvl','death')
|
||||
top_offset: 20,
|
||||
align: 'right', //('left', 'right', or 'center')
|
||||
width: 250, //(integer, or 'auto')
|
||||
delay: 3000,
|
||||
allow_dismiss: true,
|
||||
stackup_spacing: 10 // spacing between consecutive stacecked growls.
|
||||
});
|
||||
};
|
||||
$rootScope.$watch('user.stats.exp', function(after, before) {
|
||||
if (after == before) return;
|
||||
Notification.exp(after - before);
|
||||
});
|
||||
|
||||
/*
|
||||
Sets up "+1 Exp", "Level Up", etc notifications
|
||||
*/
|
||||
function setupGrowlNotifications() {
|
||||
$rootScope.$watch('user.stats.gp', function(after, before) {
|
||||
if (after == before) return;
|
||||
var money = after - before;
|
||||
Notification.gp(money);
|
||||
|
||||
function statsNotification(html, type) {
|
||||
// don't show notifications if user dead
|
||||
if (User.user.stats.lvl == 0) return;
|
||||
growlNotification(html, type);
|
||||
};
|
||||
//Append Bonus
|
||||
var bonus = User.user._tmp.streakBonus;
|
||||
|
||||
/**
|
||||
Show "+ 5 {gold_coin} 3 {silver_coin}"
|
||||
*/
|
||||
function showCoins(money) {
|
||||
var absolute, gold, silver;
|
||||
absolute = Math.abs(money);
|
||||
gold = Math.floor(absolute);
|
||||
silver = Math.floor((absolute - gold) * 100);
|
||||
if (gold && silver > 0) {
|
||||
return "" + gold + " <i class='icon-gold'></i> " + silver + " <i class='icon-silver'></i>";
|
||||
} else if (gold > 0) {
|
||||
return "" + gold + " <i class='icon-gold'></i>";
|
||||
} else if (silver > 0) {
|
||||
return "" + silver + " <i class='icon-silver'></i>";
|
||||
}
|
||||
};
|
||||
if ((money > 0) && !!bonus) {
|
||||
if (bonus < 0.01) bonus = 0.01;
|
||||
Notification.text("+ " + Notification.coins(bonus) + " Streak Bonus!");
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.stats.hp', function(after, before) {
|
||||
$rootScope.$watch('user._tmp.drop', function(after, before){
|
||||
if (after == before || !after) return;
|
||||
$rootScope.modals.drop = true;
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.achievements.streak', function(after, before){
|
||||
if(after == before || after < before) return;
|
||||
$rootScope.modals.achievements.streak = true;
|
||||
});
|
||||
|
||||
// FIXME: this isn't working for some reason
|
||||
/*_.each(['weapon', 'head', 'chest', 'shield'], function(watched){
|
||||
$rootScope.$watch('user.items.' + watched, function(before, after){
|
||||
if (after == before) return;
|
||||
var num = after - before;
|
||||
var rounded = Math.abs(num.toFixed(1));
|
||||
if (num < 0) {
|
||||
//lost hp from purchase
|
||||
statsNotification("<i class='icon-heart'></i> - " + rounded + " HP", 'hp');
|
||||
} else if (num > 0) {
|
||||
// gained hp from potion/level?
|
||||
statsNotification("<i class='icon-heart'></i> + " + rounded + " HP", 'hp');
|
||||
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);
|
||||
}
|
||||
});
|
||||
})
|
||||
});*/
|
||||
|
||||
$rootScope.$watch('user.stats.exp', function(after, before) {
|
||||
if (after == before) return;
|
||||
var num = after - before;
|
||||
var rounded = Math.abs(num.toFixed(1));
|
||||
// TODO fix hackey negative notification supress
|
||||
if (num < 0 && num > -50) {
|
||||
statsNotification("<i class='icon-star'></i> - " + rounded + " XP", 'xp');
|
||||
} else if (num > 0) {
|
||||
statsNotification("<i class='icon-star'></i> + " + rounded + " XP", 'xp');
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.stats.gp', function(after, before) {
|
||||
if (after == before) return;
|
||||
var bonus, money, sign;
|
||||
money = after - before;
|
||||
|
||||
//why is this happening? gotta find where stats.gp is being set from (-)habit
|
||||
//if (!money) {
|
||||
// return;
|
||||
//}
|
||||
|
||||
sign = money < 0 ? '-' : '+';
|
||||
statsNotification("" + sign + " " + (showCoins(money)), 'gp');
|
||||
|
||||
//Append Bonus TODO
|
||||
//bonus = model.get('_streakBonus');
|
||||
|
||||
if ((money > 0) && !!bonus) {
|
||||
if (bonus < 0.01) {
|
||||
bonus = 0.01;
|
||||
}
|
||||
statsNotification("+ " + (showCoins(bonus)) + " Streak Bonus!");
|
||||
//model.del('_streakBonus');
|
||||
}
|
||||
});
|
||||
|
||||
// FIXME
|
||||
// user.on('set', 'items.*', function(item, after, before) {
|
||||
// if ((item === 'armor' || item === 'weapon' || item === 'shield' || item === 'head') && parseInt(after) < parseInt(before)) {
|
||||
// //don't want to day "lost a head"
|
||||
// if (item === 'head') {
|
||||
// item = 'helm';
|
||||
// }
|
||||
// return statsNotification("<i class='icon-death'></i> Respawn!", "death");
|
||||
// }
|
||||
// });
|
||||
|
||||
$rootScope.$watch('user.stats.lvl', function(after, before) {
|
||||
if (after == before) return;
|
||||
if (after > before) {
|
||||
statsNotification('<i class="icon-chevron-up"></i> Level Up!', 'lvl');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
setupGrowlNotifications();
|
||||
$rootScope.$watch('user.stats.lvl', function(after, before) {
|
||||
if (after == before) return;
|
||||
if (after > before) {
|
||||
Notification.lvl();
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ habitrpg.controller("PetsCtrl", ['$scope', 'User',
|
|||
$scope.userCurrentPet = User.user.items.currentPet;
|
||||
$scope.pets = window.habitrpgShared.items.items.pets;
|
||||
$scope.hatchingPotions = window.habitrpgShared.items.items.hatchingPotions;
|
||||
$scope.totalPets = $scope.pets.length * $scope.hatchingPotions.length;
|
||||
|
||||
$scope.hasPet = function(name, potion){
|
||||
if (!$scope.userPets) return false;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http',
|
||||
function($scope, $rootScope, $location, User, $http) {
|
||||
$rootScope.modals = {};
|
||||
$rootScope.modals.achievements = {};
|
||||
$rootScope.User = User;
|
||||
$rootScope.user = User.user;
|
||||
$rootScope.settings = User.settings;
|
||||
|
|
@ -42,25 +43,57 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
|
|||
}
|
||||
|
||||
$rootScope.showStripe = function() {
|
||||
var disableAds = User.user.flags.ads == 'hide' ? '' : 'Disable Ads, ';
|
||||
StripeCheckout.open({
|
||||
key: window.env.STRIPE_PUB_KEY,
|
||||
address: false,
|
||||
amount: 500,
|
||||
name: "Checkout",
|
||||
description: "Buy 20 Gems, " + disableAds + "Support the Developers",
|
||||
panelLabel: "Checkout",
|
||||
token: function(data) {
|
||||
$scope.$apply(function(){
|
||||
$http.post("/api/v1/user/buy-gems", data)
|
||||
.success(function() {
|
||||
window.location.href = "/";
|
||||
}).error(function(err) {
|
||||
alert(err);
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
StripeCheckout.open({
|
||||
key: window.env.STRIPE_PUB_KEY,
|
||||
address: false,
|
||||
amount: 500,
|
||||
name: "Checkout",
|
||||
description: "Buy 20 Gems, Disable Ads, Support the Developers",
|
||||
panelLabel: "Checkout",
|
||||
token: function(data) {
|
||||
$scope.$apply(function(){
|
||||
$http.post("/api/v1/user/buy-gems", data)
|
||||
.success(function() {
|
||||
window.location.href = "/";
|
||||
}).error(function(err) {
|
||||
alert(err);
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$rootScope.charts = {};
|
||||
$rootScope.toggleChart = function(id, task) {
|
||||
var history = [], matrix, data, chart, options;
|
||||
switch (id) {
|
||||
case 'exp':
|
||||
$rootScope.charts.exp = !$rootScope.charts.exp;
|
||||
history = User.user.history.exp;
|
||||
break;
|
||||
case 'todos':
|
||||
$rootScope.charts.todos = !$rootScope.charts.todos;
|
||||
history = User.user.history.todos;
|
||||
break;
|
||||
default:
|
||||
$rootScope.charts[id] = !$rootScope.charts[id];
|
||||
history = task.history;
|
||||
if (task && task._editing) task._editing = false;
|
||||
}
|
||||
matrix = [['Date', 'Score']];
|
||||
_.each(history, function(obj) {
|
||||
matrix.push([moment(obj.date).format('MM/DD/YY'), obj.value]);
|
||||
});
|
||||
data = google.visualization.arrayToDataTable(matrix);
|
||||
options = {
|
||||
title: 'History',
|
||||
backgroundColor: {
|
||||
fill: 'transparent'
|
||||
},
|
||||
width:300
|
||||
};
|
||||
chart = new google.visualization.LineChart($("." + id + "-chart")[0]);
|
||||
chart.draw(data, options);
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -59,9 +59,17 @@ habitrpg.controller('SettingsCtrl',
|
|||
});
|
||||
}
|
||||
|
||||
$scope.restoreValues = {};
|
||||
$rootScope.$watch('modals.restore', function(value){
|
||||
if(value === true){
|
||||
$scope.restoreValues.stats = angular.copy(User.user.stats);
|
||||
$scope.restoreValues.items = angular.copy(User.user.items);
|
||||
}
|
||||
})
|
||||
|
||||
$scope.restore = function(){
|
||||
var stats = User.user.stats,
|
||||
items = User.user.items;
|
||||
var stats = $scope.restoreValues.stats,
|
||||
items = $scope.restoreValues.items;
|
||||
User.setMultiple({
|
||||
"stats.hp": stats.hp,
|
||||
"stats.exp": stats.exp,
|
||||
|
|
|
|||
|
|
@ -32,43 +32,12 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', '
|
|||
}
|
||||
];
|
||||
$scope.score = function(task, direction) {
|
||||
/*save current stats to compute the difference after scoring.
|
||||
*/
|
||||
|
||||
var oldStats, statsDiff;
|
||||
statsDiff = {};
|
||||
oldStats = _.clone(User.user.stats);
|
||||
if (task.type === "reward" && User.user.stats.gp < task.value){
|
||||
return Notification.text('Not enough GP.');
|
||||
}
|
||||
Algos.score(User.user, task, direction);
|
||||
/*compute the stats change.
|
||||
*/
|
||||
User.log({op: "score",data: task, dir: direction});
|
||||
|
||||
_.each(oldStats, function(value, key) {
|
||||
var newValue;
|
||||
newValue = User.user.stats[key];
|
||||
if (newValue !== value) {
|
||||
statsDiff[key] = newValue - value;
|
||||
}
|
||||
});
|
||||
/*notify user if there are changes in stats.
|
||||
*/
|
||||
|
||||
if (Object.keys(statsDiff).length > 0) {
|
||||
Notification.push({
|
||||
type: "stats",
|
||||
stats: statsDiff
|
||||
});
|
||||
}
|
||||
if (task.type === "reward" && _.isEmpty(statsDiff)) {
|
||||
Notification.push({
|
||||
type: "text",
|
||||
text: "Not enough GP."
|
||||
});
|
||||
}
|
||||
User.log({
|
||||
op: "score",
|
||||
data: task,
|
||||
dir: direction
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addTask = function(list) {
|
||||
|
|
@ -101,6 +70,13 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', '
|
|||
op: "revive"
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleEdit = function(task){
|
||||
console.log(task)
|
||||
task._editing = !task._editing;
|
||||
if($rootScope.charts[task.id]) $rootScope.charts[task.id] = false;
|
||||
};
|
||||
|
||||
$scope.remove = function(task) {
|
||||
var tasks;
|
||||
if (confirm("Are you sure you want to delete this task?") !== true) {
|
||||
|
|
@ -113,13 +89,14 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', '
|
|||
});
|
||||
tasks.splice(tasks.indexOf(task), 1);
|
||||
};
|
||||
|
||||
/*
|
||||
------------------------
|
||||
Items
|
||||
------------------------
|
||||
*/
|
||||
|
||||
$scope.$watch("user.items", function() {
|
||||
var updateStore = function(){
|
||||
var sorted, updated;
|
||||
updated = window.habitrpgShared.items.updateStore(User.user);
|
||||
/* Figure out whether we wanna put this in habitrpg-shared
|
||||
|
|
@ -127,31 +104,24 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', '
|
|||
|
||||
sorted = [updated.weapon, updated.armor, updated.head, updated.shield, updated.potion, updated.reroll];
|
||||
$scope.itemStore = sorted;
|
||||
});
|
||||
}
|
||||
|
||||
updateStore();
|
||||
|
||||
$scope.buy = function(type) {
|
||||
var hasEnough;
|
||||
hasEnough = window.habitrpgShared.items.buyItem(User.user, type);
|
||||
var hasEnough = window.habitrpgShared.items.buyItem(User.user, type);
|
||||
if (hasEnough) {
|
||||
User.log({
|
||||
op: "buy",
|
||||
type: type
|
||||
});
|
||||
Notification.push({
|
||||
type: "text",
|
||||
text: "Item bought!"
|
||||
});
|
||||
User.log({op: "buy",type: type});
|
||||
Notification.text("Item purchased.");
|
||||
updateStore();
|
||||
} else {
|
||||
Notification.push({
|
||||
type: "text",
|
||||
text: "Not enough GP."
|
||||
});
|
||||
Notification.text("Not enough GP.");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$scope.clearCompleted = function() {
|
||||
User.user.todos = _.reject(User.user.todos, {completed:true});
|
||||
User.log({op: 'clear-completed'});
|
||||
}
|
||||
|
||||
}]);
|
||||
}]);
|
||||
|
|
|
|||
|
|
@ -1,21 +1,41 @@
|
|||
"use strict";
|
||||
|
||||
habitrpg.controller("UserCtrl", ['$scope', '$location', 'User',
|
||||
function($scope, $location, User) {
|
||||
$scope.profile = User.user;
|
||||
$scope.hideUserAvatar = function() {
|
||||
$(".userAvatar").hide();
|
||||
};
|
||||
$scope.toggleHelm = function(val){
|
||||
User.log({op:'set', data:{'preferences.showHelm':val}});
|
||||
habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$http',
|
||||
function($rootScope, $scope, $location, User, $http) {
|
||||
$scope.profile = User.user;
|
||||
$scope.hideUserAvatar = function() {
|
||||
$(".userAvatar").hide();
|
||||
};
|
||||
$scope.toggleHelm = function(val){
|
||||
User.log({op:'set', data:{'preferences.showHelm':val}});
|
||||
}
|
||||
|
||||
$scope.$watch('_editing.profile', function(value){
|
||||
if(value === true) $scope.editingProfile = angular.copy(User.user.profile);
|
||||
});
|
||||
|
||||
$scope.save = function(){
|
||||
var values = {};
|
||||
_.each($scope.editingProfile, function(value, key){
|
||||
// Using toString because we need to compare two arrays (websites)
|
||||
var curVal = $scope.profile.profile[key];
|
||||
if(!curVal || $scope.editingProfile[key].toString() !== curVal.toString())
|
||||
values['profile.' + key] = value;
|
||||
});
|
||||
User.setMultiple(values);
|
||||
$scope._editing.profile = false;
|
||||
}
|
||||
|
||||
$scope.addWebsite = function(){
|
||||
if (!$scope.editingProfile.websites) $scope.editingProfile.websites = [];
|
||||
$scope.editingProfile.websites.push($scope._newWebsite);
|
||||
$scope._newWebsite = '';
|
||||
}
|
||||
$scope.removeWebsite = function($index){
|
||||
$scope.editingProfile.websites.splice($index,1);
|
||||
}
|
||||
|
||||
$scope.unlock = User.unlock;
|
||||
|
||||
}
|
||||
$scope.addWebsite = function(){
|
||||
if (!User.user.profile.websites) User.user.profile.websites = [];
|
||||
User.user.profile.websites.push($scope._newWebsite);
|
||||
User.set('profile.websites', User.user.profile.websites);
|
||||
$scope._newWebsite = '';
|
||||
}
|
||||
$scope.removeWebsite = function($index){
|
||||
User.user.profile.websites.splice($index,1);
|
||||
}
|
||||
}]);
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ habitrpg.directive('habitrpgSortable', ['User', function(User) {
|
|||
return function($scope, element, attrs, ngModel) {
|
||||
$(element).sortable({
|
||||
axis: "y",
|
||||
distance: 5,
|
||||
start: function (event, ui) {
|
||||
ui.item.data('startIndex', ui.item.index());
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
angular.module('habitrpg')
|
||||
.filter('gold', function () {
|
||||
return function (gp) {
|
||||
return Math.floor(gp);
|
||||
}
|
||||
}).filter('silver', function () {
|
||||
return function (gp) {
|
||||
return Math.floor((gp - Math.floor(gp))*100);
|
||||
}
|
||||
});
|
||||
.filter('gold', function () {
|
||||
return function (gp) {
|
||||
return Math.floor(gp);
|
||||
}
|
||||
}).filter('silver', function () {
|
||||
return function (gp) {
|
||||
return Math.floor((gp - Math.floor(gp))*100);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,9 +5,15 @@
|
|||
*/
|
||||
|
||||
angular.module('guideServices', []).
|
||||
factory('Guide', ['User', function(User) {
|
||||
factory('Guide', ['$rootScope', 'User', 'Items', 'Helpers', function($rootScope, User, Items, Helpers) {
|
||||
|
||||
var initTour = function() {
|
||||
/**
|
||||
* Init and show the welcome tour. Note we do it listening to a $rootScope broadcasted 'userLoaded' message,
|
||||
* this because we need to determine whether to show the tour *after* the user has been pulled from the server,
|
||||
* otherwise it's always start off as true, and then get set to false later
|
||||
*/
|
||||
$rootScope.$on('userUpdated', initTour);
|
||||
function initTour(){
|
||||
if (User.user.flags.showTour === false) return;
|
||||
var tourSteps = [
|
||||
{
|
||||
|
|
@ -46,12 +52,12 @@ angular.module('guideServices', []).
|
|||
}
|
||||
];
|
||||
_.each(tourSteps, function(step){
|
||||
step.content = "<div><div class='NPC-Justin'></div>" + step.content + "</div>"; // add Justin NPC img
|
||||
step.content = "<div><div class='NPC-Justin float-left'></div>" + step.content + "</div>"; // add Justin NPC img
|
||||
});
|
||||
$('.main-herobox').popover('destroy');
|
||||
var tour = new Tour({
|
||||
onEnd: function(){
|
||||
User.log({op:'set',data:{'flags.showTour':false}});
|
||||
User.set('flags.showTour', false);
|
||||
}
|
||||
});
|
||||
tourSteps.forEach(function(step) {
|
||||
|
|
@ -61,9 +67,90 @@ angular.module('guideServices', []).
|
|||
//tour.start(true);
|
||||
};
|
||||
|
||||
var alreadyShown = function(before, after) {
|
||||
return !(!before && after === true);
|
||||
};
|
||||
|
||||
var showPopover = function(selector, title, html, placement) {
|
||||
if (!placement) placement = 'bottom';
|
||||
$(selector).popover('destroy');
|
||||
var button = "<button class='btn btn-sm btn-default' onClick=\"$('" + selector + "').popover('hide');return false;\">Close</button>";
|
||||
html = "<div><div class='NPC-Justin float-left'></div>" + html + '<br/>' + button + '</div>';
|
||||
$(selector).popover({
|
||||
title: title,
|
||||
placement: placement,
|
||||
trigger: 'manual',
|
||||
html: true,
|
||||
content: html
|
||||
}).popover('show');
|
||||
};
|
||||
|
||||
$rootScope.$watch('user.flags.customizationsNotification', function(after, before) {
|
||||
if (alreadyShown(before, after)) return;
|
||||
showPopover('.main-herobox', 'Customize Your Avatar', "Click your avatar to customize your appearance.", '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');
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.flags.petsEnabled', function(after, before) {
|
||||
//TODO is this ever used? I think dropsEnabled is the one that's used
|
||||
if (alreadyShown(before, after)) return;
|
||||
var html = "You have unlocked Pets! You can now buy pets with Gems (note, you replenish Gems with real-life money - so chose your pets wisely!)";
|
||||
showPopover('#rewardsTabs', 'Pets Unlocked', 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');
|
||||
});
|
||||
|
||||
$rootScope.$watch('user.flags.dropsEnabled', function(after, before) {
|
||||
if (alreadyShown(before, after)) return;
|
||||
var drop = Helpers.randomVal(Items.items.pets);
|
||||
var eggs = User.user.items.eggs || [];
|
||||
eggs.push(drop); // FIXME put this on server instead
|
||||
User.set('items.eggs', eggs);
|
||||
$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.$watch('user.items', function(after, before) {
|
||||
if (User.user.achievements && User.user.achievements.ultimateGear) return;
|
||||
var items = User.user.items;
|
||||
if (+items.weapon >= 6 && +items.armor >= 5 && +items.head >= 5 && +items.shield >= 5) {
|
||||
User.set('achievements.ultimateGear', true); // FIXME
|
||||
$('#max-gear-achievement-modal').modal('show'); // FIXME
|
||||
}
|
||||
});
|
||||
|
||||
// FIXME how to handle tasks.*.streak?
|
||||
// FIXME move to tasksCtrl
|
||||
/*$rootScope.$watch('user.tasks.*.streak', function(id, after, before) {
|
||||
if (after > 0) {
|
||||
if ((after % 21) === 0) {
|
||||
user.incr('achievements.streak', 1)
|
||||
return $('#streak-achievement-modal').modal('show');
|
||||
} else if ((before - after === 1) && (before % 21 === 0)) {
|
||||
return user.incr('achievements.streak', -1);
|
||||
}
|
||||
}
|
||||
});*/
|
||||
|
||||
return {
|
||||
initTour: initTour
|
||||
}
|
||||
initTour:initTour
|
||||
};
|
||||
|
||||
}
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
*/
|
||||
|
||||
angular.module('memberServices', ['ngResource']).
|
||||
factory('Members', ['API_URL', '$resource',
|
||||
function(API_URL, $resource) {
|
||||
factory('Members', ['$rootScope', 'API_URL', '$resource',
|
||||
function($rootScope, API_URL, $resource) {
|
||||
var members = {};
|
||||
var Member = $resource(API_URL + '/api/v1/members/:uid', {uid:'@_id'});
|
||||
return {
|
||||
var memberServices = {
|
||||
|
||||
members: members,
|
||||
|
||||
|
|
@ -69,5 +69,11 @@ angular.module('memberServices', ['ngResource']).
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rootScope.$on('userUpdated', function(event, user){
|
||||
memberServices.populate(user);
|
||||
})
|
||||
|
||||
return memberServices;
|
||||
}
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,75 +1,71 @@
|
|||
|
||||
angular.module("notificationServices", []).factory("Notification", function() {
|
||||
var active, data, fixMe, timer;
|
||||
fixMe = {
|
||||
push: function() {},
|
||||
get: function() {},
|
||||
animate: function() {},
|
||||
clearTimer: function() {},
|
||||
init: function() {}
|
||||
};
|
||||
return fixMe;
|
||||
data = {
|
||||
message: ""
|
||||
};
|
||||
active = false;
|
||||
timer = null;
|
||||
return {
|
||||
hide: function() {
|
||||
$("#notification").fadeOut(function() {
|
||||
$("#notification").css("webkit-transform", "none");
|
||||
$("#notification").css("top", "-63px");
|
||||
$("#notification").css("left", "0px");
|
||||
setTimeout((function() {
|
||||
$("#notification").show();
|
||||
}), 190);
|
||||
/**
|
||||
Set up "+1 Exp", "Level Up", etc notifications
|
||||
*/
|
||||
angular.module("notificationServices", [])
|
||||
.factory("Notification", ['User', function(User) {
|
||||
function growl(html, type) {
|
||||
$.bootstrapGrowl(html, {
|
||||
ele: '#notification-area',
|
||||
type: type, //(null, 'info', 'error', 'success', 'gp', 'xp', 'hp', 'lvl','death')
|
||||
top_offset: 20,
|
||||
align: 'right', //('left', 'right', or 'center')
|
||||
width: 250, //(integer, or 'auto')
|
||||
delay: 3000,
|
||||
allow_dismiss: true,
|
||||
stackup_spacing: 10 // spacing between consecutive stacecked growls.
|
||||
});
|
||||
active = false;
|
||||
timer = null;
|
||||
},
|
||||
animate: function() {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(this.hide, 2000);
|
||||
};
|
||||
|
||||
/**
|
||||
Show "+ 5 {gold_coin} 3 {silver_coin}"
|
||||
*/
|
||||
function coins(money) {
|
||||
var absolute, gold, silver;
|
||||
absolute = Math.abs(money);
|
||||
gold = Math.floor(absolute);
|
||||
silver = Math.floor((absolute - gold) * 100);
|
||||
if (gold && silver > 0) {
|
||||
return "" + gold + " <i class='icon-gold'></i> " + silver + " <i class='icon-silver'></i>";
|
||||
} else if (gold > 0) {
|
||||
return "" + gold + " <i class='icon-gold'></i>";
|
||||
} else if (silver > 0) {
|
||||
return "" + silver + " <i class='icon-silver'></i>";
|
||||
}
|
||||
if (active === false) {
|
||||
active = true;
|
||||
$("#notification").transition({
|
||||
y: 63,
|
||||
x: 0
|
||||
});
|
||||
timer = setTimeout(this.hide, 2000);
|
||||
}
|
||||
},
|
||||
push: function(message) {
|
||||
data.message = "";
|
||||
switch (message.type) {
|
||||
case "stats":
|
||||
if ((message.stats.exp != null) && (message.stats.gp != null)) {
|
||||
data.message = "Experience: " + message.stats.exp + "<br />GP: " + message.stats.gp.toFixed(2);
|
||||
}
|
||||
if (message.stats.hp) {
|
||||
data.message = "HP: " + message.stats.hp.toFixed(2);
|
||||
}
|
||||
if (message.stats.gp && !(message.stats.exp != null)) {
|
||||
data.message = "<br />GP: " + message.stats.gp.toFixed(2);
|
||||
}
|
||||
break;
|
||||
case "text":
|
||||
data.message = message.text;
|
||||
}
|
||||
this.animate();
|
||||
},
|
||||
get: function() {
|
||||
return data;
|
||||
},
|
||||
clearTimer: function() {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
active = false;
|
||||
},
|
||||
init: function() {
|
||||
timer = setTimeout(this.hide, 2000);
|
||||
};
|
||||
|
||||
var sign = function(number){
|
||||
return number?number<0?'-':'+':'+';
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var round = function(number){
|
||||
return Math.abs(number.toFixed(1));
|
||||
}
|
||||
|
||||
return {
|
||||
coins: coins,
|
||||
hp: function(val) {
|
||||
// don't show notifications if user dead
|
||||
if (User.user.stats.lvl == 0) return;
|
||||
growl("<i class='icon-heart'></i> " + sign(val) + " " + round(val) + " HP", 'hp');
|
||||
},
|
||||
exp: function(val) {
|
||||
if (User.user.stats.lvl == 0) return;
|
||||
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');
|
||||
},
|
||||
gp: function(val) {
|
||||
if (User.user.stats.lvl == 0) return;
|
||||
growl(sign(val) + " " + coins(val), 'gp');
|
||||
},
|
||||
text: function(val){
|
||||
growl(val);
|
||||
},
|
||||
lvl: function(){
|
||||
growl('<i class="icon-chevron-up"></i> Level Up!', 'lvl');
|
||||
},
|
||||
death: function(){
|
||||
growl("<i class='icon-death'></i> Respawn!", "death");
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -5,209 +5,235 @@
|
|||
*/
|
||||
|
||||
angular.module('userServices', []).
|
||||
factory('User', ['$http', '$location', 'API_URL', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID', 'Members',
|
||||
function($http, $location, API_URL, STORAGE_USER_ID, STORAGE_SETTINGS_ID, Members) {
|
||||
var authenticated = false,
|
||||
defaultSettings = {
|
||||
auth: { apiId: '', apiToken: ''},
|
||||
sync: {
|
||||
queue: [], //here OT will be queued up, this is NOT call-back queue!
|
||||
sent: [] //here will be OT which have been sent, but we have not got reply from server yet.
|
||||
},
|
||||
fetching: false, // whether fetch() was called or no. this is to avoid race conditions
|
||||
online: false
|
||||
},
|
||||
settings = {}, //habit mobile settings (like auth etc.) to be stored here
|
||||
user = {}; // this is stored as a reference accessible to all controllers, that way updates propagate
|
||||
factory('User', ['$rootScope', '$http', '$location', '$window', 'API_URL', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID',
|
||||
function($rootScope, $http, $location, $window, API_URL, STORAGE_USER_ID, STORAGE_SETTINGS_ID) {
|
||||
var authenticated = false,
|
||||
defaultSettings = {
|
||||
auth: { apiId: '', apiToken: ''},
|
||||
sync: {
|
||||
queue: [], //here OT will be queued up, this is NOT call-back queue!
|
||||
sent: [] //here will be OT which have been sent, but we have not got reply from server yet.
|
||||
},
|
||||
fetching: false, // whether fetch() was called or no. this is to avoid race conditions
|
||||
online: false
|
||||
},
|
||||
settings = {}, //habit mobile settings (like auth etc.) to be stored here
|
||||
user = {}; // this is stored as a reference accessible to all controllers, that way updates propagate
|
||||
|
||||
//first we populate user with schema
|
||||
_.extend(user, window.habitrpgShared.helpers.newUser());
|
||||
user.apiToken = user._id = ''; // we use id / apitoken to determine if registered
|
||||
//first we populate user with schema
|
||||
_.extend(user, window.habitrpgShared.helpers.newUser());
|
||||
user.apiToken = user._id = ''; // we use id / apitoken to determine if registered
|
||||
|
||||
//than we try to load localStorage
|
||||
|
||||
if (localStorage.getItem(STORAGE_USER_ID)) {
|
||||
_.extend(user, JSON.parse(localStorage.getItem(STORAGE_USER_ID)));
|
||||
}
|
||||
//than we try to load localStorage
|
||||
if (localStorage.getItem(STORAGE_USER_ID)) {
|
||||
_.extend(user, JSON.parse(localStorage.getItem(STORAGE_USER_ID)));
|
||||
}
|
||||
|
||||
var syncQueue = function (cb) {
|
||||
if (!authenticated) {
|
||||
alert("Not authenticated, can't sync, go to settings first.");
|
||||
return;
|
||||
}
|
||||
|
||||
var queue = settings.sync.queue;
|
||||
var sent = settings.sync.sent;
|
||||
if (queue.length === 0) {
|
||||
console.log('Sync: Queue is empty');
|
||||
return;
|
||||
}
|
||||
if (settings.fetching) {
|
||||
console.log('Sync: Already fetching');
|
||||
return;
|
||||
}
|
||||
if (settings.online!==true) {
|
||||
console.log('Sync: Not online');
|
||||
return;
|
||||
}
|
||||
|
||||
settings.fetching = true;
|
||||
// move all actions from queue array to sent array
|
||||
_.times(queue.length, function () {
|
||||
sent.push(queue.shift());
|
||||
});
|
||||
|
||||
$http.post(API_URL + '/api/v1/user/batch-update', sent, {params: {data:+new Date, _v:user._v}})
|
||||
.success(function (data, status, heacreatingders, config) {
|
||||
data.tasks = _.toArray(data.tasks);
|
||||
//make sure there are no pending actions to sync. If there are any it is not safe to apply model from server as we may overwrite user data.
|
||||
if (!queue.length) {
|
||||
//we can't do user=data as it will not update user references in all other angular controllers.
|
||||
|
||||
// the user has been modified from another application, sync up
|
||||
if(data.wasModified) {
|
||||
delete data.wasModified;
|
||||
_.extend(user, data);
|
||||
}
|
||||
user._v = data._v;
|
||||
Members.populate(user);
|
||||
|
||||
// FIXME handle this somewhere else, we don't need to check every single time
|
||||
var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60)
|
||||
if (!user.preferences.timezoneOffset || user.preferences.timezoneOffset !== offset) {
|
||||
userServices.log({op:'set', data: {'preferences.timezoneOffset': offset}});
|
||||
}
|
||||
}
|
||||
sent.length = 0;
|
||||
settings.fetching = false;
|
||||
save();
|
||||
if (cb) {
|
||||
cb(false)
|
||||
}
|
||||
|
||||
syncQueue(); // call syncQueue to check if anyone pushed more actions to the queue while we were talking to server.
|
||||
})
|
||||
.error(function (data, status, headers, config) {
|
||||
//move sent actions back to queue
|
||||
_.times(sent.length, function () {
|
||||
queue.push(sent.shift())
|
||||
});
|
||||
settings.fetching = false;
|
||||
//Notification.push({type:'text', text:"We're offline"})
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
var save = function () {
|
||||
localStorage.setItem(STORAGE_USER_ID, JSON.stringify(user));
|
||||
localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(settings));
|
||||
};
|
||||
var userServices = {
|
||||
user: user,
|
||||
online: function (status) {
|
||||
if (status===true) {
|
||||
settings.online = true;
|
||||
syncQueue();
|
||||
} else {
|
||||
settings.online = false;
|
||||
};
|
||||
},
|
||||
|
||||
authenticate: function (uuid, token, cb) {
|
||||
if (!!uuid && !!token) {
|
||||
$http.defaults.headers.common['x-api-user'] = uuid;
|
||||
$http.defaults.headers.common['x-api-key'] = token;
|
||||
authenticated = true;
|
||||
settings.auth.apiId = uuid;
|
||||
settings.auth.apiToken = token;
|
||||
settings.online = true;
|
||||
if (user && user._v) user._v--; // shortcut to always fetch new updates on page reload
|
||||
this.log({}, cb);
|
||||
} else {
|
||||
alert('Please enter your ID and Token in settings.')
|
||||
}
|
||||
},
|
||||
|
||||
authenticated: function(){
|
||||
return this.settings.auth.apiId !== "";
|
||||
},
|
||||
|
||||
log: function (action, cb) {
|
||||
//push by one buy one if an array passed in.
|
||||
if (_.isArray(action)) {
|
||||
action.forEach(function (a) {
|
||||
settings.sync.queue.push(a);
|
||||
});
|
||||
} else {
|
||||
settings.sync.queue.push(action);
|
||||
}
|
||||
|
||||
save();
|
||||
syncQueue(cb);
|
||||
},
|
||||
|
||||
/*
|
||||
Very simple path-set. `set('preferences.gender','m')` for example. We'll deprecate this once we have a complete API
|
||||
*/
|
||||
set: function(k, v) {
|
||||
var self = userServices;
|
||||
var log = { op: 'set', data: {} };
|
||||
window.habitrpgShared.helpers.dotSet(k, v, userServices.user);
|
||||
log.data[k] = v;
|
||||
userServices.log(log);
|
||||
},
|
||||
|
||||
setMultiple: function(obj){
|
||||
var self = this;
|
||||
var log = { op: 'set', data: {} };
|
||||
_.each(obj, function(v,k){
|
||||
window.habitrpgShared.helpers.dotSet(k, v, userServices.user);
|
||||
log.data[k] = v;
|
||||
})
|
||||
userServices.log(log);
|
||||
},
|
||||
|
||||
save: save,
|
||||
|
||||
settings: settings
|
||||
};
|
||||
|
||||
|
||||
//load settings if we have them
|
||||
if (localStorage.getItem(STORAGE_SETTINGS_ID)) {
|
||||
//use extend here to make sure we keep object reference in other angular controllers
|
||||
_.extend(settings, JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID)));
|
||||
|
||||
//if settings were saved while fetch was in process reset the flag.
|
||||
settings.fetching = false;
|
||||
//create and load if not
|
||||
} else {
|
||||
localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(defaultSettings));
|
||||
_.extend(settings, defaultSettings);
|
||||
var syncQueue = function (cb) {
|
||||
if (!authenticated) {
|
||||
$window.alert("Not authenticated, can't sync, go to settings first.");
|
||||
return;
|
||||
}
|
||||
|
||||
//If user does not have ApiID that forward him to settings.
|
||||
if (!settings.auth.apiId || !settings.auth.apiToken) {
|
||||
//var search = $location.search(); // FIXME this should be working, but it's returning an empty object when at a root url /?_id=...
|
||||
var search = $location.search(window.location.search.substring(1)).$$search; // so we use this fugly hack instead
|
||||
if (search.err) return alert(search.err);
|
||||
if (search._id && search.apiToken) {
|
||||
userServices.authenticate(search._id, search.apiToken, function(){
|
||||
window.location.href='/';
|
||||
var queue = settings.sync.queue;
|
||||
var sent = settings.sync.sent;
|
||||
if (queue.length === 0) {
|
||||
// Sync: Queue is empty
|
||||
return;
|
||||
}
|
||||
if (settings.fetching) {
|
||||
// Sync: Already fetching
|
||||
return;
|
||||
}
|
||||
if (settings.online!==true) {
|
||||
// Sync: Not online
|
||||
return;
|
||||
}
|
||||
|
||||
settings.fetching = true;
|
||||
// move all actions from queue array to sent array
|
||||
_.times(queue.length, function () {
|
||||
sent.push(queue.shift());
|
||||
});
|
||||
|
||||
$http.post(API_URL + '/api/v1/user/batch-update', sent, {params: {data:+new Date, _v:user._v}})
|
||||
.success(function (data, status, heacreatingders, config) {
|
||||
data.tasks = _.toArray(data.tasks);
|
||||
//make sure there are no pending actions to sync. If there are any it is not safe to apply model from server as we may overwrite user data.
|
||||
if (!queue.length) {
|
||||
//we can't do user=data as it will not update user references in all other angular controllers.
|
||||
|
||||
// the user has been modified from another application, sync up
|
||||
if(data.wasModified) {
|
||||
delete data.wasModified;
|
||||
_.extend(user, data);
|
||||
$rootScope.$emit('userUpdated', user);
|
||||
}
|
||||
user._v = data._v;
|
||||
|
||||
// FIXME handle this somewhere else, we don't need to check every single time
|
||||
var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60)
|
||||
if (!user.preferences.timezoneOffset || user.preferences.timezoneOffset !== offset) {
|
||||
userServices.log({op:'set', data: {'preferences.timezoneOffset': offset}});
|
||||
}
|
||||
}
|
||||
sent.length = 0;
|
||||
settings.fetching = false;
|
||||
save();
|
||||
if (cb) {
|
||||
cb(false)
|
||||
}
|
||||
|
||||
syncQueue(); // call syncQueue to check if anyone pushed more actions to the queue while we were talking to server.
|
||||
})
|
||||
.error(function (data, status, headers, config) {
|
||||
//move sent actions back to queue
|
||||
_.times(sent.length, function () {
|
||||
queue.push(sent.shift())
|
||||
});
|
||||
settings.fetching = false;
|
||||
//Notification.push({type:'text', text:"We're offline"})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var save = function () {
|
||||
localStorage.setItem(STORAGE_USER_ID, JSON.stringify(user));
|
||||
localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(settings));
|
||||
};
|
||||
var userServices = {
|
||||
user: user,
|
||||
online: function (status) {
|
||||
if (status===true) {
|
||||
settings.online = true;
|
||||
syncQueue();
|
||||
} else {
|
||||
settings.online = false;
|
||||
};
|
||||
},
|
||||
|
||||
authenticate: function (uuid, token, cb) {
|
||||
if (!!uuid && !!token) {
|
||||
$http.defaults.headers.common['x-api-user'] = uuid;
|
||||
$http.defaults.headers.common['x-api-key'] = token;
|
||||
authenticated = true;
|
||||
settings.auth.apiId = uuid;
|
||||
settings.auth.apiToken = token;
|
||||
settings.online = true;
|
||||
if (user && user._v) user._v--; // shortcut to always fetch new updates on page reload
|
||||
userServices.log({}, cb);
|
||||
} else {
|
||||
alert('Please enter your ID and Token in settings.')
|
||||
}
|
||||
},
|
||||
|
||||
authenticated: function(){
|
||||
return this.settings.auth.apiId !== "";
|
||||
},
|
||||
|
||||
log: function (action, cb) {
|
||||
//push by one buy one if an array passed in.
|
||||
if (_.isArray(action)) {
|
||||
action.forEach(function (a) {
|
||||
settings.sync.queue.push(a);
|
||||
});
|
||||
} else {
|
||||
if (window.location.pathname.indexOf('/static') !== 0){
|
||||
localStorage.clear();
|
||||
window.location.href = '/logout';
|
||||
settings.sync.queue.push(action);
|
||||
}
|
||||
|
||||
save();
|
||||
syncQueue(cb);
|
||||
},
|
||||
|
||||
/*
|
||||
Very simple path-set. `set('preferences.gender','m')` for example. We'll deprecate this once we have a complete API
|
||||
*/
|
||||
set: function(k, v) {
|
||||
var log = { op: 'set', data: {} };
|
||||
$window.habitrpgShared.helpers.dotSet(k, v, this.user);
|
||||
log.data[k] = v;
|
||||
userServices.log(log);
|
||||
},
|
||||
|
||||
setMultiple: function(obj){
|
||||
var log = { op: 'set', data: {} };
|
||||
_.each(obj, function(v,k){
|
||||
$window.habitrpgShared.helpers.dotSet(k, v, userServices.user);
|
||||
log.data[k] = v;
|
||||
});
|
||||
userServices.log(log);
|
||||
},
|
||||
|
||||
/**
|
||||
* For gem-unlockable preferences, (a) if owned, select preference (b) else, purchase
|
||||
* @param path: User.preferences <-> User.purchased maps like User.preferences.skin=abc <-> User.purchased.skin.abc.
|
||||
* Pass in this paramater as "skin.abc". Alternatively, pass as an array ["skin.abc", "skin.xyz"] to unlock sets
|
||||
*/
|
||||
unlock: function(path){
|
||||
var self = userServices; //this; // why isn't this working?
|
||||
|
||||
if (_.isArray(path)) {
|
||||
if (confirm("Purchase for 5 Gems?") !== true) return;
|
||||
if (user.balance < 1.25) return $rootScope.modals.buyGems = true;
|
||||
path = path.join(',');
|
||||
} else {
|
||||
if (window.habitrpgShared.helpers.dotGet('purchased.' + path, user)) {
|
||||
var pref = path.split('.')[0],
|
||||
val = path.split('.')[1];
|
||||
return self.set('preferences.' + pref, val);
|
||||
} else {
|
||||
if (confirm("Purchase for 2 Gems?") !== true) return;
|
||||
if (user.balance < 0.5) return $rootScope.modals.buyGems = true;
|
||||
}
|
||||
}
|
||||
|
||||
$http.post(API_URL + '/api/v1/user/unlock?path=' + path)
|
||||
.success(function(data, status, headers, config){
|
||||
self.log({}); // sync new unlocked & preferences
|
||||
}).error(function(data, status, headers, config){
|
||||
alert(status + ': ' + data);
|
||||
//FIXME use method used elsewhere for handling this error, this is temp while developing
|
||||
})
|
||||
},
|
||||
|
||||
save: save,
|
||||
|
||||
settings: settings
|
||||
};
|
||||
|
||||
|
||||
//load settings if we have them
|
||||
if (localStorage.getItem(STORAGE_SETTINGS_ID)) {
|
||||
//use extend here to make sure we keep object reference in other angular controllers
|
||||
_.extend(settings, JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID)));
|
||||
|
||||
//if settings were saved while fetch was in process reset the flag.
|
||||
settings.fetching = false;
|
||||
//create and load if not
|
||||
} else {
|
||||
localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(defaultSettings));
|
||||
_.extend(settings, defaultSettings);
|
||||
}
|
||||
|
||||
//If user does not have ApiID that forward him to settings.
|
||||
if (!settings.auth.apiId || !settings.auth.apiToken) {
|
||||
//var search = $location.search(); // FIXME this should be working, but it's returning an empty object when at a root url /?_id=...
|
||||
var search = $location.search($window.location.search.substring(1)).$$search; // so we use this fugly hack instead
|
||||
if (search.err) return alert(search.err);
|
||||
if (search._id && search.apiToken) {
|
||||
userServices.authenticate(search._id, search.apiToken, function(){
|
||||
$window.location.href='/';
|
||||
});
|
||||
} else {
|
||||
userServices.authenticate(settings.auth.apiId, settings.auth.apiToken)
|
||||
if ($window.location.pathname.indexOf('/static') !== 0){
|
||||
localStorage.clear();
|
||||
$window.location.href = '/logout';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
userServices.authenticate(settings.auth.apiId, settings.auth.apiToken)
|
||||
}
|
||||
|
||||
return userServices;
|
||||
|
||||
return userServices;
|
||||
}
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
window.habitrpg = angular.module('habitrpg', ['userServices', 'memberServices'])
|
||||
window.habitrpg = angular.module('habitrpg', ['userServices'])
|
||||
.constant("API_URL", "")
|
||||
.constant("STORAGE_USER_ID", 'habitrpg-user')
|
||||
.constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings')
|
||||
|
|
@ -37,7 +37,8 @@ api.auth = function(req, res, next) {
|
|||
if (_.isEmpty(user)) {
|
||||
return res.json(401, NO_USER_FOUND);
|
||||
}
|
||||
res.locals.wasModified = +user._v !== +req.query._v;
|
||||
|
||||
res.locals.wasModified = req.query._v ? +user._v !== +req.query._v : true;
|
||||
res.locals.user = user;
|
||||
req.session.userId = user._id;
|
||||
return next();
|
||||
|
|
|
|||
|
|
@ -189,21 +189,18 @@ api.postChat = function(req, res, next) {
|
|||
api.deleteChatMessage = function(req, res, next){
|
||||
var user = res.locals.user
|
||||
var group = res.locals.group;
|
||||
var message = _.find(group.chat, {id: req.params.messageId, uuid: user.id});
|
||||
var message = _.find(group.chat, {id: req.params.messageId});
|
||||
|
||||
if(message === undefined) return res.json(404, {err: "Message not found!"});
|
||||
|
||||
if(user.id !== message.uuid){
|
||||
if(!user.backer || (user.backer && !user.backer.admin)){
|
||||
return res.json(401, {err: "Not authorized to delete this message!"})
|
||||
}
|
||||
if(user.id !== message.uuid && !(user.backer && user.backer.admin)){
|
||||
return res.json(401, {err: "Not authorized to delete this message!"})
|
||||
}
|
||||
|
||||
group.chat = _.without(group.chat, message);
|
||||
|
||||
group.save(function(err, data){
|
||||
if(err) return res.json(500, {err: err});
|
||||
|
||||
res.send(204);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
// fixme remove this junk, was coffeescript compiled (probably for IE8 compat)
|
||||
var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||
|
||||
var url = require('url');
|
||||
var ipn = require('paypal-ipn');
|
||||
var _ = require('lodash');
|
||||
var nconf = require('nconf');
|
||||
var async = require('async');
|
||||
|
|
@ -202,13 +204,10 @@ api.scoreTask = function(req, res, next) {
|
|||
}
|
||||
task = user.tasks[id];
|
||||
delta = algos.score(user, task, direction);
|
||||
return user.save(function(err, saved) {
|
||||
if (err) {
|
||||
return res.json(500, {
|
||||
err: err
|
||||
});
|
||||
}
|
||||
return res.json(200, _.extend({
|
||||
//user.markModified('flags');
|
||||
user.save(function(err, saved) {
|
||||
if (err) return res.json(500, {err: err});
|
||||
res.json(200, _.extend({
|
||||
delta: delta
|
||||
}, saved.toJSON().stats));
|
||||
});
|
||||
|
|
@ -565,6 +564,49 @@ api['delete'] = function(req, res) {
|
|||
})
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------
|
||||
Unlock Preferences
|
||||
------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
api.unlock = function(req, res) {
|
||||
var user = res.locals.user;
|
||||
var path = req.query.path;
|
||||
var fullSet = ~path.indexOf(',');
|
||||
|
||||
// 5G per set, 2G per individual
|
||||
cost = fullSet ? 1.25 : 0.5;
|
||||
|
||||
if (user.balance < cost)
|
||||
return res.json(401, {err: 'Not enough gems'});
|
||||
|
||||
if (fullSet) {
|
||||
var paths = path.split(',');
|
||||
_.each(paths, function(p){
|
||||
helpers.dotSet('purchased.' + p, true, user);
|
||||
});
|
||||
} else {
|
||||
if (helpers.dotGet('purchased.' + path, user) === true)
|
||||
return res.json(401, {err: 'User already purchased that'});
|
||||
helpers.dotSet('purchased.' + path, true, user);
|
||||
}
|
||||
|
||||
user.balance -= cost;
|
||||
user.__v++;
|
||||
user.markModified('purchased');
|
||||
user.save(function(err, saved){
|
||||
if (err) res.json(500, {err:err});
|
||||
res.send(200);
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------
|
||||
Buy Gems
|
||||
------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
Setup Stripe response when posting payment
|
||||
|
|
@ -585,7 +627,7 @@ api.buyGems = function(req, res) {
|
|||
},
|
||||
function(response, cb) {
|
||||
res.locals.user.balance += 5;
|
||||
res.locals.user.flags.ads = 'hide';
|
||||
res.locals.user.purchased.ads = true;
|
||||
res.locals.user.save(cb);
|
||||
}
|
||||
], function(err, saved){
|
||||
|
|
@ -594,6 +636,31 @@ api.buyGems = function(req, res) {
|
|||
});
|
||||
};
|
||||
|
||||
api.buyGemsPaypalIPN = function(req, res) {
|
||||
res.send(200);
|
||||
ipn.verify(req.body, function callback(err, msg) {
|
||||
if (err) {
|
||||
console.error(msg);
|
||||
res.send(500, msg);
|
||||
} else {
|
||||
if (req.body.payment_status == 'Completed') {
|
||||
//Payment has been confirmed as completed
|
||||
var parts = url.parse(req.body.custom, true);
|
||||
var uid = parts.query.uid; //, apiToken = query.apiToken;
|
||||
if (!uid) throw new Error("uuid or apiToken not found when completing paypal transaction");
|
||||
User.findById(uid, function(err, user) {
|
||||
if (err) throw err;
|
||||
if (_.isEmpty(user)) throw "user not found with uuid " + uuid + " when completing paypal transaction"
|
||||
user.balance += 5;
|
||||
user.purchased.ads = true;
|
||||
user.save();
|
||||
console.log('PayPal transaction completed and user updated');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------
|
||||
Tags
|
||||
|
|
@ -712,7 +779,15 @@ api.batchUpdate = function(req, res, next) {
|
|||
}
|
||||
response = user.toJSON();
|
||||
response.wasModified = res.locals.wasModified;
|
||||
res.json(200, response);
|
||||
return console.log("Reply sent");
|
||||
if (response._tmp && response._tmp.drop) response.wasModified = true;
|
||||
|
||||
// Send the response to the server
|
||||
if(response.wasModified){
|
||||
res.json(200, response);
|
||||
}else{
|
||||
res.json(200, {_v: response._v});
|
||||
}
|
||||
|
||||
return;
|
||||
});
|
||||
};
|
||||
|
|
@ -54,7 +54,6 @@ var walk = function(folder){
|
|||
walk(path.join(__dirname, "/../build"));
|
||||
|
||||
var getBuildUrl = function(url){
|
||||
console.log("CALLED ", url)
|
||||
if(buildFiles[url]) return buildFiles[url];
|
||||
|
||||
return url;
|
||||
|
|
@ -64,6 +63,8 @@ module.exports.locals = function(req, res, next) {
|
|||
res.locals.habitrpg = res.locals.habitrpg || {}
|
||||
_.defaults(res.locals.habitrpg, {
|
||||
NODE_ENV: nconf.get('NODE_ENV'),
|
||||
BASE_URL: nconf.get('BASE_URL'),
|
||||
PAYPAL_MERCHANT: nconf.get('PAYPAL_MERCHANT'),
|
||||
IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
|
||||
STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'),
|
||||
getBuildUrl: getBuildUrl
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
// User.js
|
||||
// =======
|
||||
// Defines the user data model (schema) for use via the API.
|
||||
|
||||
// Dependencies
|
||||
// ------------
|
||||
var mongoose = require("mongoose");
|
||||
var Schema = mongoose.Schema;
|
||||
var helpers = require('habitrpg-shared/script/helpers');
|
||||
var _ = require('lodash');
|
||||
|
||||
// User Schema
|
||||
// -----------
|
||||
|
||||
var UserSchema = new Schema({
|
||||
// ### UUID and API Token
|
||||
_id: {
|
||||
type: String,
|
||||
'default': helpers.uuid
|
||||
|
|
@ -13,7 +23,8 @@ var UserSchema = new Schema({
|
|||
'default': helpers.uuid
|
||||
},
|
||||
|
||||
//We want to know *every* time an object updates. Mongoose uses __v to designate when an object contains arrays which
|
||||
// ### Mongoose Update Object
|
||||
// We want to know *every* time an object updates. Mongoose uses __v to designate when an object contains arrays which
|
||||
// have been updated (http://goo.gl/gQLz41), but we want *every* update
|
||||
_v: {
|
||||
type: Number,
|
||||
|
|
@ -42,15 +53,14 @@ var UserSchema = new Schema({
|
|||
loggedin: Date
|
||||
}
|
||||
},
|
||||
/* TODO*/
|
||||
|
||||
backer: Schema.Types.Mixed,
|
||||
/*
|
||||
# tier: Number
|
||||
# admin: Boolean
|
||||
# contributor: Boolean
|
||||
# tokensApplieds: Boolean
|
||||
*/
|
||||
backer: {
|
||||
tier: Number,
|
||||
admin: Boolean,
|
||||
npc: String,
|
||||
contributor: String,
|
||||
tokensApplied: Boolean
|
||||
},
|
||||
|
||||
balance: Number,
|
||||
habitIds: Array,
|
||||
|
|
@ -59,12 +69,18 @@ var UserSchema = new Schema({
|
|||
rewardIds: Array,
|
||||
filters: {type: Schema.Types.Mixed, 'default': {}},
|
||||
|
||||
purchased: {
|
||||
ads: {type: Boolean, 'default': false},
|
||||
skin: {type: Schema.Types.Mixed, 'default': {}}, // eg, {skeleton: true, pumpkin: true, eb052b: true}
|
||||
hair: {type: Schema.Types.Mixed, 'default': {}}
|
||||
},
|
||||
|
||||
flags: {
|
||||
customizationsNotification: {type: Boolean, 'default': false},
|
||||
showTour: {type: Boolean, 'default': true},
|
||||
ads: {type: String, 'default': 'show'}, // FIXME make this a boolean, run migration
|
||||
dropsEnabled: {type: Boolean, 'default': false},
|
||||
itemsEnabled: {type: Boolean, 'default': false},
|
||||
newStuff: {type: String, 'default': 'hide'}, //FIXME to boolean (currently show/hide)
|
||||
newStuff: {type: Boolean, 'default': false},
|
||||
rewrite: {type: Boolean, 'default': true},
|
||||
partyEnabled: Boolean, // FIXME do we need this?
|
||||
petsEnabled: {type: Boolean, 'default': false},
|
||||
|
|
@ -121,33 +137,38 @@ var UserSchema = new Schema({
|
|||
|
||||
eggs: [
|
||||
{
|
||||
dialog: String, //You've found a Wolf Egg! Find a hatching potion to pour on this egg, and one day it will hatch into a loyal pet
|
||||
name: String, // Wolf
|
||||
notes: String, //Find a hatching potion to pour on this egg, and one day it will hatch into a loyal pet.
|
||||
text: String, // Wolf
|
||||
//type: String, //Egg // this is forcing mongoose to return object as "[object Object]", but I don't think this is needed anyway?
|
||||
value: Number //3
|
||||
// example: You've found a Wolf Egg! Find a hatching potion to pour on this egg, and one day it will hatch into a loyal pet
|
||||
dialog: String,
|
||||
// example: Wolf
|
||||
name: String,
|
||||
// example: Find a hatching potion to pour on this egg, and one day it will hatch into a loyal pet.
|
||||
notes: String,
|
||||
// example: Wolf
|
||||
text: String,
|
||||
/* type: String, //Egg // this is forcing mongoose to return object as "[object Object]", but I don't think this is needed anyway? */
|
||||
// example: 3
|
||||
value: Number
|
||||
}
|
||||
],
|
||||
hatchingPotions: Array, //["Base", "Skeleton",...]
|
||||
hatchingPotions: Array, // ["Base", "Skeleton",...]
|
||||
lastDrop: {
|
||||
date: {type: Date, 'default': Date.now},
|
||||
count: {type: Number, 'default': 0}
|
||||
},
|
||||
/* ["BearCub-Base", "Cactus-Base", ...]*/
|
||||
// ["BearCub-Base", "Cactus-Base", ...]
|
||||
|
||||
pets: Array
|
||||
},
|
||||
/*FIXME store as Date?*/
|
||||
// FIXME store as Date?
|
||||
|
||||
lastCron: {
|
||||
type: Date,
|
||||
'default': Date.now
|
||||
},
|
||||
/* FIXME remove?*/
|
||||
// FIXME remove?
|
||||
|
||||
party: {
|
||||
//party._id //FIXME make these populate docs?
|
||||
//party._id // FIXME make these populate docs?
|
||||
current: String, // party._id
|
||||
invitation: String, // party._id
|
||||
lastMessageSeen: String,
|
||||
|
|
@ -155,19 +176,19 @@ var UserSchema = new Schema({
|
|||
},
|
||||
preferences: {
|
||||
armorSet: String,
|
||||
dayStart: Number,
|
||||
gender: String,
|
||||
hair: String,
|
||||
hideHeader: Boolean,
|
||||
showHelm: Boolean,
|
||||
skin: String,
|
||||
dayStart: {type:Number, 'default': 0},
|
||||
gender: {type:String, 'default': 'm'},
|
||||
hair: {type:String, 'default':'blond'},
|
||||
hideHeader: {type:Boolean, 'default':false},
|
||||
showHelm: {type:Boolean, 'default':true},
|
||||
skin: {type:String, 'default':'white'},
|
||||
timezoneOffset: Number
|
||||
},
|
||||
profile: {
|
||||
blurb: String,
|
||||
imageUrl: String,
|
||||
name: String,
|
||||
websites: Array //["http://ocdevel.com" ]
|
||||
websites: Array // styled like --> ["http://ocdevel.com" ]
|
||||
},
|
||||
stats: {
|
||||
hp: Number,
|
||||
|
|
@ -177,15 +198,15 @@ var UserSchema = new Schema({
|
|||
},
|
||||
tags: [
|
||||
{
|
||||
/* FIXME use refs?*/
|
||||
// FIXME use refs?
|
||||
id: String,
|
||||
name: String
|
||||
}
|
||||
],
|
||||
/*
|
||||
# We can't define `tasks` until we move off Derby, since Derby requires dictionary of objects. When we're off, migrate
|
||||
# to array of subdocs
|
||||
*/
|
||||
|
||||
// ### Tasks Definition
|
||||
// We can't define `tasks` until we move off Derby, since Derby requires dictionary of objects. When we're off, migrate
|
||||
// to array of subdocs
|
||||
|
||||
tasks: Schema.Types.Mixed
|
||||
/*
|
||||
|
|
@ -208,12 +229,13 @@ var UserSchema = new Schema({
|
|||
strict: true
|
||||
});
|
||||
|
||||
/**
|
||||
Derby requires a strange storage format for somethign called "refLists". Here we hook into loading the data, so we
|
||||
can provide a more "expected" storage format for our various helper methods. Since the attributes are passed by reference,
|
||||
the underlying data will be modified too - so when we save back to the database, it saves it in the way Derby likes.
|
||||
This will go away after the rewrite is complete
|
||||
*/
|
||||
// Legacy Derby Function?
|
||||
// ----------------------
|
||||
// Derby requires a strange storage format for somethign called "refLists". Here we hook into loading the data, so we
|
||||
// can provide a more "expected" storage format for our various helper methods. Since the attributes are passed by reference,
|
||||
// the underlying data will be modified too - so when we save back to the database, it saves it in the way Derby likes.
|
||||
// This will go away after the rewrite is complete
|
||||
|
||||
function transformTaskLists(doc) {
|
||||
_.each(['habit', 'daily', 'todo', 'reward'], function(type) {
|
||||
// we use _.transform instead of a simple _.where in order to maintain sort-order
|
||||
|
|
@ -235,20 +257,23 @@ UserSchema.methods.toJSON = function() {
|
|||
doc.id = doc._id;
|
||||
transformTaskLists(doc); // we need to also transform for our server-side routes
|
||||
|
||||
// FIXME? Is this a reference to `doc.filters` or just disabled code? Remove?
|
||||
/*
|
||||
// Remove some unecessary data as far as client consumers are concerned
|
||||
//_.each(['habit', 'daily', 'todo', 'reward'], function(type) {
|
||||
// delete doc["#{type}Ids"]
|
||||
//});
|
||||
//delete doc.tasks
|
||||
*/
|
||||
doc.filters = {};
|
||||
doc._tmp = this._tmp; // be sure to send down drop notifs
|
||||
|
||||
return doc;
|
||||
};
|
||||
|
||||
/*
|
||||
# FIXME - since we're using special @post('init') above, we need to flag when the original path was modified.
|
||||
# Custom setter/getter virtuals?
|
||||
*/
|
||||
// FIXME - since we're using special @post('init') above, we need to flag when the original path was modified.
|
||||
// Custom setter/getter virtuals?
|
||||
|
||||
UserSchema.pre('save', function(next) {
|
||||
this.markModified('tasks');
|
||||
//our own version incrementer
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ router.post('/user/revive', auth.auth, cron, user.revive);
|
|||
router.post('/user/batch-update', auth.auth, cron, user.batchUpdate);
|
||||
router.post('/user/reroll', auth.auth, cron, user.reroll);
|
||||
router.post('/user/buy-gems', auth.auth, user.buyGems);
|
||||
router.post('/user/buy-gems/paypal-ipn', user.buyGemsPaypalIPN);
|
||||
router.post('/user/unlock', auth.auth, cron, user.unlock);
|
||||
router.post('/user/reset', auth.auth, user.reset);
|
||||
router['delete']('/user', auth.auth, user['delete']);
|
||||
|
||||
|
|
|
|||
6
test/README.md
Normal file
6
test/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
We need to clean up this directory. The *real* tests are in spec/ mock/ e2e/ and api.mocha.coffee. We want to:
|
||||
|
||||
1. Move all old / deprecated tests from casper, test2, etc into spec, mock, e2e
|
||||
1. Move api.mocha.coffee to vanilla javascript
|
||||
1. Remove dependency of api.mocha.coffee on Derby, port it to Mongoose
|
||||
1. Add better test-coverage
|
||||
38
test/spec/authCtrlSpec.js
Normal file
38
test/spec/authCtrlSpec.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
'use strict';
|
||||
|
||||
describe('Auth Controller', function() {
|
||||
|
||||
beforeEach(module('habitrpg'));
|
||||
|
||||
describe('AuthCtrl', function(){
|
||||
var scope, ctrl, user, $httpBackend, $window;
|
||||
|
||||
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
|
||||
$httpBackend = _$httpBackend_;
|
||||
scope = $rootScope.$new();
|
||||
scope.loginUsername = 'user'
|
||||
scope.loginPassword = 'pass'
|
||||
$window = { location: { href: ""}, alert: sinon.spy() };
|
||||
user = { user: {}, authenticate: sinon.spy() };
|
||||
|
||||
ctrl = $controller('AuthCtrl', {$scope: scope, $window: $window, User: user});
|
||||
}));
|
||||
|
||||
it('should log in users with correct uname / pass', function() {
|
||||
$httpBackend.expectPOST('/api/v1/user/auth/local').respond({id: 'abc', token: 'abc'});
|
||||
scope.auth();
|
||||
$httpBackend.flush();
|
||||
expect(user.authenticate).to.have.been.calledOnce;
|
||||
expect($window.alert).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should not log in users with incorrect uname / pass', function() {
|
||||
$httpBackend.expectPOST('/api/v1/user/auth/local').respond(404, '');
|
||||
scope.auth();
|
||||
$httpBackend.flush();
|
||||
expect(user.authenticate).to.not.have.been.called;
|
||||
expect($window.alert).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
40
test/spec/memberServicesSpec.js
Normal file
40
test/spec/memberServicesSpec.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
'use strict';
|
||||
|
||||
describe('memberServices', function() {
|
||||
var $httpBackend, members;
|
||||
|
||||
beforeEach(module('memberServices'));
|
||||
beforeEach(module('habitrpg'));
|
||||
|
||||
beforeEach(inject(function(_$httpBackend_, Members) {
|
||||
$httpBackend = _$httpBackend_;
|
||||
members = Members;
|
||||
}));
|
||||
|
||||
it('has no members at the beginning', function() {
|
||||
expect(members.members).to.be.an('object');
|
||||
expect(members.members).to.eql({});
|
||||
expect(members.selectedMember).to.be.undefined;
|
||||
});
|
||||
|
||||
it('populates members', function(){
|
||||
var uid = 'abc';
|
||||
members.populate({
|
||||
members: [{ _id: uid }]
|
||||
});
|
||||
expect(members.members).to.eql({
|
||||
abc: { _id: uid }
|
||||
});
|
||||
});
|
||||
|
||||
it('selects a member', function(){
|
||||
var uid = 'abc';
|
||||
$httpBackend.expectGET('/api/v1/members/' + uid).respond({ _id: uid });
|
||||
members.selectMember(uid);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(members.selectedMember._id).to.eql(uid);
|
||||
expect(members.members).to.have.property(uid);
|
||||
});
|
||||
|
||||
});
|
||||
49
test/spec/userServicesSpec.js
Normal file
49
test/spec/userServicesSpec.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
'use strict';
|
||||
|
||||
describe('userServices', function() {
|
||||
var $httpBackend, $window, user, STORAGE_USER_ID, STORAGE_SETTINGS_ID;
|
||||
|
||||
beforeEach(module('habitrpg'));
|
||||
|
||||
beforeEach(function(){
|
||||
module(function($provide){
|
||||
$window = {href: '', alert: sinon.spy(), location: {search: '', pathname: ''}};
|
||||
$provide.value('$window', $window);
|
||||
});
|
||||
|
||||
inject(function(_$httpBackend_, User, _STORAGE_USER_ID_, _STORAGE_SETTINGS_ID_) {
|
||||
$httpBackend = _$httpBackend_;
|
||||
user = User;
|
||||
STORAGE_USER_ID = _STORAGE_USER_ID_;
|
||||
STORAGE_SETTINGS_ID = _STORAGE_SETTINGS_ID_;
|
||||
});
|
||||
localStorage.removeItem(STORAGE_SETTINGS_ID);
|
||||
localStorage.removeItem(STORAGE_USER_ID);
|
||||
});
|
||||
|
||||
it('checks online status', function(){
|
||||
user.online(true);
|
||||
expect(user.settings.online).to.be.true;
|
||||
user.online(false);
|
||||
expect(user.settings.online).to.be.false;
|
||||
})
|
||||
|
||||
it('saves user data to local storage', function(){
|
||||
user.save();
|
||||
var settings = JSON.parse(localStorage[STORAGE_SETTINGS_ID]);
|
||||
var user_id = JSON.parse(localStorage[STORAGE_USER_ID]);
|
||||
expect(settings).to.eql(user.settings);
|
||||
expect(user_id).to.eql(user.user);
|
||||
});
|
||||
|
||||
it('alerts when not authenticated', function(){
|
||||
user.log();
|
||||
expect($window.alert).to.have.been.calledWith("Not authenticated, can't sync, go to settings first.");
|
||||
});
|
||||
|
||||
it('puts items in que queue', function(){
|
||||
user.log({});
|
||||
//TODO where does that null comes from?
|
||||
expect(user.settings.sync.queue).to.eql([null, {}]);
|
||||
});
|
||||
});
|
||||
|
|
@ -27,6 +27,7 @@ html
|
|||
- }else{
|
||||
// Remember to update the file list in Gruntfile.js!
|
||||
script(type='text/javascript', src='/bower_components/jquery/jquery.min.js')
|
||||
script(type='text/javascript', src='/bower_components/jquery.cookie/jquery.cookie.js')
|
||||
script(type='text/javascript', src='/bower_components/bootstrap-growl/jquery.bootstrap-growl.min.js')
|
||||
script(type='text/javascript', src='/bower_components/bootstrap-tour/build/js/bootstrap-tour.min.js')
|
||||
script(type='text/javascript', src='/bower_components/angular/angular.min.js')
|
||||
|
|
@ -101,7 +102,7 @@ html
|
|||
div(ng-if='user.preferences.hideHeader')
|
||||
include ./shared/header/menu
|
||||
|
||||
.exp-chart(ng-show='page.charts.exp')
|
||||
.exp-chart(ng-show='charts.exp')
|
||||
|
||||
#main(ng-view)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ form(ng-submit='postChat(group,_chatMessage)')
|
|||
td
|
||||
input.btn.chat-btn(type='submit', value='Send Chat')
|
||||
td
|
||||
button.btn
|
||||
i(class='pull-right icon-refresh', ng-click='sync(group)', tooltip='Fetch Recent Messages')
|
||||
button.btn(type="button", ng-click='sync(group)', tooltip='Fetch Recent Messages')
|
||||
i(class='pull-right icon-refresh')
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
a.pull-right.gem-wallet(rel='popover', data-trigger='hover', data-title='Guild Bank', data-content='Gems which your Guild leader can use for prizes in the upcoming <a target=_blank href="https://trello.com/card/challenges-individual-party-guild-public/50e5d3684fe3a7266b0036d6/58">Challenges</a> feature.', data-placement='bottom', data-html='true')
|
||||
a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title='Guild Bank', popover='Gems which your Guild leader can use for prizes in the upcoming Challenges feature.', popover-placement='left', popover-html='true')
|
||||
// <span class="task-action-btn tile flush bright add-gems-btn">+</span>
|
||||
span.task-action-btn.tile.flush.neutral
|
||||
.Gems
|
||||
|
|
|
|||
|
|
@ -42,13 +42,7 @@
|
|||
include ./groups/index
|
||||
|
||||
.tab-pane#inventory-tab
|
||||
.row-fluid
|
||||
.span6.border-right
|
||||
h2 Inventory
|
||||
include ./inventory
|
||||
.span6
|
||||
h2 Market
|
||||
include ./market
|
||||
include ./inventory
|
||||
|
||||
.tab-pane#stable-tab
|
||||
include ./pets
|
||||
|
|
|
|||
|
|
@ -1,23 +1,54 @@
|
|||
.row-fluid(ng-controller='InventoryCtrl')
|
||||
div(ng-class='{span6: hatching}')
|
||||
.span6.border-right
|
||||
h2 Inventory
|
||||
p.well Combine eggs with hatching potions to make pets by clicking an egg and then a potion. Sell unwanted drops to Alexander the Merchant.
|
||||
menu.inventory-list(type='list')
|
||||
li.customize-menu
|
||||
menu.pets-menu(label='Eggs')
|
||||
menu.pets-menu(label='Eggs ({{userEggs.length}})')
|
||||
p(ng-show='userEggs.length < 1') You don't have any eggs yet.
|
||||
div(ng-repeat='egg in userEggs track by $index')
|
||||
button.customize-option(tooltip='{{egg.text}}', ng-click='chooseEgg(egg)', class='Pet_Egg_{{egg.name}}')
|
||||
button.customize-option(tooltip='{{egg.text}}', ng-click='chooseEgg(egg, $index)', class='Pet_Egg_{{egg.name}}', ng-class='selectableInventory(egg, selectedPotion.name, $index)')
|
||||
p {{egg.text}}
|
||||
li.customize-menu
|
||||
menu.hatchingPotion-menu(label='Hatching Potions')
|
||||
menu.hatchingPotion-menu(label='Hatching Potions ({{userHatchingPotions.length}})')
|
||||
p(ng-show='userHatchingPotions.length < 1') You don't have any hatching potions yet.
|
||||
div(ng-repeat='hatchingPotion in userHatchingPotions track by $index')
|
||||
button.customize-option(tooltip='{{hatchingPotion}}', class='Pet_HatchingPotion_{{hatchingPotion}}')
|
||||
button.customize-option(tooltip='{{hatchingPotion}}', ng-click='choosePotion(hatchingPotion, $index)', class='Pet_HatchingPotion_{{hatchingPotion}}', ng-class='selectableInventory(selectedEgg, hatchingPotion, $index)')
|
||||
p {{hatchingPotion}}
|
||||
.span6(ng-show='hatching')
|
||||
h3 Hatch Your Egg
|
||||
p(ng-show='userHatchingPotions.length < 1') You don't have any hatching potions yet.
|
||||
div(ng-show='userHatchingPotions')
|
||||
p.
|
||||
Which hatching potion will you pour on your <b>{{selectedEgg.text}}</b> egg?
|
||||
select(ng-options='hatchingPotion for hatchingPotion in userHatchingPotions', ng-model="selectedPotion")
|
||||
button(ng-click="pour()") Pour
|
||||
|
||||
.span6
|
||||
h2 Market
|
||||
.row-fluid(ng-controller='MarketCtrl')
|
||||
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') Alexander the Merchant
|
||||
.popover-content
|
||||
p
|
||||
| Welcome to the Market. Dying to get that particular pet you're after, but don't want to wait for it to drop? Buy it here! If you have unwanted drops, sell them to me.
|
||||
p
|
||||
button.btn.btn-primary(ng-show='selectedEgg', ng-click='sellInventory()')
|
||||
| Sell {{selectedEgg.name}} for {{selectedEgg.value}} GP
|
||||
button.btn.btn-primary(ng-show='selectedPotion', ng-click='sellInventory()')
|
||||
| Sell {{selectedPotion.name}} for {{selectedPotion.value}} GP
|
||||
|
||||
menu.inventory-list(type='list')
|
||||
li.customize-menu
|
||||
menu.pets-menu(label='Eggs')
|
||||
div(ng-repeat='egg in eggs track by $index')
|
||||
button.customize-option(tooltip='{{egg.text}} - {{egg.value}} Gem(s)', ng-click='buy("egg", egg)', class='Pet_Egg_{{egg.name}}')
|
||||
p {{egg.text}}
|
||||
|
||||
li.customize-menu
|
||||
menu.pets-menu(label='Hatching Potions')
|
||||
div(ng-repeat='hatchingPotion in hatchingPotions track by $index')
|
||||
button.customize-option(tooltip='{{hatchingPotion.text}} - {{hatchingPotion.value}} Gem(s)', ng-click='buy("hatchingPotion", hatchingPotion)', class='Pet_HatchingPotion_{{hatchingPotion.name}}')
|
||||
p {{hatchingPotion.text}}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
.row-fluid(ng-controller='MarketCtrl')
|
||||
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') Alexander Augustin
|
||||
.popover-content
|
||||
| Welcome to the Market! I'm the merchant, Alexander. Dying to get that particular pet you're after, but don't want to wait for it to drop? Buy it here!
|
||||
|
||||
menu.inventory-list(type='list')
|
||||
li.customize-menu
|
||||
menu.pets-menu(label='Eggs')
|
||||
div(ng-repeat='egg in eggs track by $index')
|
||||
button.customize-option(tooltip='{{egg.text}}', ng-click='buy("egg", egg)', class='Pet_Egg_{{egg.name}}')
|
||||
p {{egg.text}}
|
||||
|
||||
li.customize-menu
|
||||
menu.pets-menu(label='Hatching Potions')
|
||||
div(ng-repeat='hatchingPotion in hatchingPotions track by $index')
|
||||
button.customize-option(tooltip='{{hatchingPotion.text}}', ng-click='buy("hatchingPotion", hatchingPotion)', class='Pet_HatchingPotion_{{hatchingPotion.name}}')
|
||||
p {{hatchingPotion.text}}
|
||||
|
||||
|
||||
|
||||
|
|
@ -11,22 +11,19 @@
|
|||
| ! Until that day,
|
||||
a(target='_blank', href='https://f.cloud.github.com/assets/2374703/164631/3ed5fa6c-78cd-11e2-8743-f65ac477b55e.png') have a look-see
|
||||
| at all the pets you can collect.
|
||||
h4 {{userPets.length}} / 90 Pets Found
|
||||
h4 {{userPets.length}} / {{totalPets}} Pets Found
|
||||
|
||||
table.pets(ng-repeat='pet in pets')
|
||||
tbody
|
||||
tr
|
||||
td(ng-repeat='potion in hatchingPotions')
|
||||
button(class="pet-button Pet-{{pet.name}}-{{potion.name}}", ng-show='hasPet(pet.name, potion.name)', ng-class="{active: isCurrentPet(pet.name, potion.name)}", ng-click='choosePet(pet.name, potion.name)', tooltip='{{potion.name}} {{pet.name}}')
|
||||
menu.pets(type='list')
|
||||
li.customize-menu(ng-repeat='pet in pets')
|
||||
menu
|
||||
div(ng-repeat='potion in hatchingPotions', tooltip='{{potion.name}} {{pet.name}}')
|
||||
button(class="pet-button Pet-{{pet.name}}-{{potion.name}}", ng-show='hasPet(pet.name, potion.name)', ng-class="{active: isCurrentPet(pet.name, potion.name)}", ng-click='choosePet(pet.name, potion.name)')
|
||||
button(class="pet-button pet-not-owned", ng-hide='hasPet(pet.name, potion.name)')
|
||||
img(src='/bower_components/habitrpg-shared/img/PixelPaw.png', tooltip='{{potion.name}} {{pet.name}}')
|
||||
img(src='/bower_components/habitrpg-shared/img/PixelPaw.png')
|
||||
|
||||
h4 Rare Pets
|
||||
table
|
||||
tr
|
||||
td
|
||||
button(class="pet-button Pet-Wolf-Veteran", ng-show='hasPet("Wolf", "Veteran")', ng-class='{active: isCurrentPet("Wolf", "Veteran")}', ng-click='choosePet("Wolf", "Veteran")', tooltip='Veteran Wolf')
|
||||
button(class="pet-button pet-not-owned", ng-hide='hasPet("Wolf", "Veteran")')
|
||||
img(src='/bower_components/habitrpg-shared/img/PixelPaw.png')
|
||||
td(ng-if='hasPet("Wolf", "Cerberus")')
|
||||
menu
|
||||
div(ng-if='hasPet("Wolf", "Veteran")')
|
||||
button(class="pet-button Pet-Wolf-Veteran", ng-class='{active: isCurrentPet("Wolf", "Veteran")}', ng-click='choosePet("Wolf", "Veteran")', tooltip='Veteran Wolf')
|
||||
div(ng-if='hasPet("Wolf", "Cerberus")')
|
||||
button(class="pet-button Pet-Wolf-Cerberus", ng-class='{active: isCurrentPet("Wolf", "Cerberus")}', ng-click='choosePet("Wolf", "Cerberus")', tooltip='Cerberus Pup')
|
||||
|
|
@ -6,11 +6,14 @@
|
|||
// gender
|
||||
li.customize-menu
|
||||
menu(label='Head')
|
||||
menu
|
||||
button.m_head_0.customize-option(type='button', ng-click='set("preferences.gender","m")')
|
||||
button.f_head_0.customize-option(type='button', ng-click='set("preferences.gender","f")')
|
||||
label.checkbox
|
||||
input(type='checkbox', ng-model='user.preferences.showHelm', ng-change='toggleHelm(user.preferences.showHelm)')
|
||||
| Show Helm
|
||||
hr
|
||||
|
||||
// hair
|
||||
li.customize-menu
|
||||
menu(label='Hair')
|
||||
|
|
@ -18,14 +21,53 @@
|
|||
button(class='{{user.preferences.gender}}_hair_black customize-option', type='button', ng-click='set("preferences.hair","black")')
|
||||
button(class='{{user.preferences.gender}}_hair_brown customize-option', type='button', ng-click='set("preferences.hair","brown")')
|
||||
button(class='{{user.preferences.gender}}_hair_white customize-option', type='button', ng-click='set("preferences.hair","white")')
|
||||
hr
|
||||
|
||||
// skin
|
||||
li.customize-menu
|
||||
menu(label='Skin')
|
||||
button.customize-option(class='{{user.preferences.gender}}_skin_dead', type='button', ng-click='set("preferences.skin","dead")')
|
||||
button.customize-option(class='{{user.preferences.gender}}_skin_orc', type='button', ng-click='set("preferences.skin","orc")')
|
||||
button.customize-option(class='{{user.preferences.gender}}_skin_asian', type='button', ng-click='set("preferences.skin","asian")')
|
||||
button.customize-option(class='{{user.preferences.gender}}_skin_black', type='button', ng-click='set("preferences.skin","black")')
|
||||
button.customize-option(class='{{user.preferences.gender}}_skin_white', type='button', ng-click='set("preferences.skin","white")')
|
||||
menu(label='Basic Skins')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_asian', ng-click='set("preferences.skin","asian")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_white', ng-click='set("preferences.skin","white")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_ea8349', ng-click='set("preferences.skin","ea8349")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_c06534', ng-click='set("preferences.skin","c06534")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_98461a', ng-click='set("preferences.skin","98461a")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_black', ng-click='set("preferences.skin","black")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_dead', ng-click='set("preferences.skin","dead")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_orc', ng-click='set("preferences.skin","orc")')
|
||||
|
||||
// Rainbow Skin
|
||||
h5.
|
||||
Rainbow Skins - 2<span class="Pet_Currency_Gem1x inline-gems"/>/skin
|
||||
//menu(label='Rainbow Skins (2G / skin)')
|
||||
menu
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_eb052b', ng-class='{locked: !user.purchased.skin.eb052b}', ng-click='unlock("skin.eb052b")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_f69922', ng-class='{locked: !user.purchased.skin.f69922}', ng-click='unlock("skin.f69922")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_f5d70f', ng-class='{locked: !user.purchased.skin.f5d70f}', ng-click='unlock("skin.f5d70f")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_0ff591', ng-class='{locked: !user.purchased.skin.0ff591}', ng-click='unlock("skin.0ff591")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_2b43f6', ng-class='{locked: !user.purchased.skin.2b43f6}', ng-click='unlock("skin.2b43f6")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_d7a9f7', ng-class='{locked: !user.purchased.skin.d7a9f7}', ng-click='unlock("skin.d7a9f7")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_800ed0', ng-class='{locked: !user.purchased.skin.800ed0}', ng-click='unlock("skin.800ed0")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_rainbow', ng-class='{locked: !user.purchased.skin.rainbow}', ng-click='unlock("skin.rainbow")')
|
||||
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"])') Unlock Set - 5<span class="Pet_Currency_Gem1x inline-gems"/>
|
||||
|
||||
// Special Events
|
||||
div.well.limited-edition
|
||||
.label.label-info.pull-right(popover='Available for purchase until November 10th (but permanently in your options if purchased).', popover-title='Limited Edition', popover-placement='right', popover-trigger='mouseenter')
|
||||
| Limited Edition
|
||||
i.icon.icon-question-sign
|
||||
h5.
|
||||
Spooky Skins - 2<span class="Pet_Currency_Gem1x inline-gems"/>/skin
|
||||
//menu(label='Spooky Skins (2 Gems / skin)')
|
||||
menu
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_monster', ng-class='{locked: !user.purchased.skin.monster}', ng-click='unlock("skin.monster")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_pumpkin', ng-class='{locked: !user.purchased.skin.pumpkin}', ng-click='unlock("skin.pumpkin")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_skeleton', ng-class='{locked: !user.purchased.skin.skeleton}', ng-click='unlock("skin.skeleton")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_zombie', ng-class='{locked: !user.purchased.skin.zombie}', ng-click='unlock("skin.zombie")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_ghost', ng-class='{locked: !user.purchased.skin.ghost}', ng-click='unlock("skin.ghost")')
|
||||
button.customize-option(type='button', class='{{user.preferences.gender}}_skin_shadow', ng-class='{locked: !user.purchased.skin.shadow}', ng-click='unlock("skin.shadow")')
|
||||
button.btn.btn-small.btn-primary(ng-hide='user.purchased.skin.monster && user.purchased.skin.pumpkin && user.purchased.skin.skeleton && user.purchased.skin.zombie && user.purchased.skin.ghost && user.purchased.skin.shadow', ng-click='unlock(["skin.monster", "skin.pumpkin", "skin.skeleton", "skin.zombie", "skin.ghost", "skin.shadow"])') Unlock Set - 5<span class="Pet_Currency_Gem1x inline-gems"/>
|
||||
|
||||
|
||||
menu(ng-show='user.preferences.gender=="f"', type='list')
|
||||
li.customize-menu
|
||||
menu(label='Clothing')
|
||||
|
|
@ -39,7 +81,7 @@
|
|||
// ------- Edit -------
|
||||
.span4
|
||||
button.btn.btn-default(ng-click='_editing.profile = true', ng-show='!_editing.profile') Edit
|
||||
button.btn.btn-primary(ng-click='_editing.profile = false', ng-show='_editing.profile') Save
|
||||
button.btn.btn-primary(ng-click='save()', ng-show='_editing.profile') Save
|
||||
div(ng-show='!_editing.profile')
|
||||
h4 Display Name
|
||||
span(ng-show='profile.profile.name') {{profile.profile.name}}
|
||||
|
|
@ -55,23 +97,23 @@
|
|||
//{{profile.profile.blurb | linky:'_blank'}}
|
||||
|
||||
h4 Websites
|
||||
ul(ng-show='profile.profile.websites')
|
||||
ul(ng-show='profile.profile.websites.length > 0')
|
||||
// TODO let's remove links eventually, since we can do markdown on profiles
|
||||
li(ng-repeat='website in profile.profile.websites')
|
||||
a(target='_blank', ng-href='{{website}}') {{website}}
|
||||
span.muted(ng-hide='profile.profile.websites') - None -
|
||||
span.muted(ng-hide='profile.profile.websites.length > 0') - None -
|
||||
|
||||
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 Display Name
|
||||
input.option-content(type='text', placeholder='Full Name', ng-model='user.profile.name', ng-blur='set("profile.name", user.profile.name)', )
|
||||
input.option-content(type='text', placeholder='Full Name', ng-model='editingProfile.name')
|
||||
.control-group.option-large
|
||||
label.control-label Photo Url
|
||||
input.option-content(type='url', ng-model='user.profile.imageUrl', placeholder='Image Url', ng-blur='set("profile.imageUrl", user.profile.imageUrl)')
|
||||
input.option-content(type='url', ng-model='editingProfile.imageUrl', placeholder='Image Url')
|
||||
.control-group.option-large
|
||||
label.control-label Blurb
|
||||
textarea.option-content(style='height:15em;', placeholder='Blurb', ng-model='user.profile.blurb', ng-blur='set("profile.blurb", user.profile.blurb)')
|
||||
textarea.option-content(style='height:15em;', placeholder='Blurb', ng-model='editingProfile.blurb')
|
||||
include ../shared/formatting-help
|
||||
.control-group.option-large
|
||||
label.control-label Websites
|
||||
|
|
@ -79,7 +121,7 @@
|
|||
input.option-content(type='url', ng-model='_newWebsite', placeholder='Add Website')
|
||||
ul
|
||||
// would prefer if there were and index in #each, instead using data-website to search with indexOf
|
||||
li(ng-repeat='website in user.profile.websites')
|
||||
li(ng-repeat='website in editingProfile.websites')
|
||||
| {{website}}
|
||||
a(ng-click='removeWebsite($index)')
|
||||
i.icon-remove
|
||||
|
|
|
|||
|
|
@ -11,10 +11,14 @@ footer.footer(ng-controller='FooterCtrl')
|
|||
.span3
|
||||
h4 Company
|
||||
ul.unstyled
|
||||
li
|
||||
.btn.btn-small.btn-success(ng-click='modals.buyGems = true')
|
||||
i.icon-heart.icon-white
|
||||
| Donate
|
||||
li
|
||||
a(href='/static/about') About
|
||||
li
|
||||
a(target='_blank', href='http://habitrpg.tumblr.com/') Blog
|
||||
a(target='_blank', href='http://blog.habitrpg.com/') Blog
|
||||
li
|
||||
a(href='/static/extensions') Extensions
|
||||
li
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title='Gems', popover="Used for buying special items (reroll, eggs, hatching potions, etc). You'll need to unlock those features before being able to use Gems.", popover-placement='bottom')
|
||||
span.task-action-btn.tile.flush.bright.add-gems-btn(ng-click='showStripe()') +
|
||||
a.pull-right.gem-wallet(ng-click='modals.buyGems = true', popover-trigger='mouseenter', popover-title='Gems', popover="Used for buying special items (reroll, eggs, hatching potions, etc). You'll need to unlock those features before being able to use Gems.", popover-placement='bottom')
|
||||
span.task-action-btn.tile.flush.bright.add-gems-btn +
|
||||
span.task-action-btn.tile.flush.neutral
|
||||
.Gems
|
||||
| {{user.balance * 4 | number:0}} Gems
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
| {{user.stats.exp | number:0}} / {{tnl(user.stats.lvl)}}
|
||||
// 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(x-bind='click:toggleChart', ng-click='notPorted()', data-id='exp', tooltip='Progress')
|
||||
a(ng-click='toggleChart("exp")', tooltip='Progress')
|
||||
i.icon-signal
|
||||
// party
|
||||
span(ng-controller='PartyCtrl')
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
h1.task-action-btn.tile.solid.user-reporter {{username(user.auth, user.profile.name)}}
|
||||
ul.flyout-content.nav.stacked
|
||||
li
|
||||
a.task-action-btn.tile.solid(x-bind='click:toggleGamePane')
|
||||
a.task-action-btn.tile.solid
|
||||
span(ng-show='viewingOptions', ng-click='gotoTasks()')
|
||||
i.icon-ok
|
||||
| Tasks
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ div(modal='modals.achievements.streak')
|
|||
.achievement.achievement-thermometer
|
||||
| You have stacked your "Streaker" Achievement! Every 21 days of streak, you gain 1 achievement point here.
|
||||
.modal-footer
|
||||
button.btn.btn-default.cancel(ng-click='modals.modals.streakAchievement = false') Cancel
|
||||
button.btn.btn-default.cancel(ng-click='modals.achievements.streak = false') Cancel
|
||||
|
||||
// Max Gear
|
||||
div(modal='modals.achievements.maxGear')
|
||||
|
|
@ -17,7 +17,7 @@ div(modal='modals.achievements.maxGear')
|
|||
.achievement.achievement-armor
|
||||
| You have earned the "Ultimate Gear" Achievement for upgrading to the maximum gear set!
|
||||
.modal-footer
|
||||
button.btn.btn-default.cancel(ng-click='modals.modals.streakAchievement = false') Cancel
|
||||
button.btn.btn-default.cancel(ng-click='modals.achievements.maxGear = false') Cancel
|
||||
|
||||
// Beast Master
|
||||
div(modal='modals.achievements.beastmaster')
|
||||
|
|
@ -28,6 +28,6 @@ div(modal='modals.achievements.beastmaster')
|
|||
.achievement.achievement-rat
|
||||
| You have earned the "Beast Master" Achievement for collecting all the pets!
|
||||
.modal-footer
|
||||
button.btn.btn-default.cancel(ng-click='modals.modals.beastMaster = false') Cancel
|
||||
button.btn.btn-default.cancel(ng-click='modals.achievements.beastmaster = false') Cancel
|
||||
|
||||
|
||||
|
|
|
|||
32
views/shared/modals/buy-gems.jade
Normal file
32
views/shared/modals/buy-gems.jade
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
div(modal='modals.buyGems')
|
||||
.modal-header
|
||||
include ../gems
|
||||
h3 Buy Gems / Donate
|
||||
|
||||
.modal-body
|
||||
table
|
||||
tr
|
||||
td
|
||||
.NPC-Justin
|
||||
td
|
||||
.popover.static-popover.fade.right.in.wide-popover
|
||||
.arrow
|
||||
h3.popover-title
|
||||
a(target='_blank', href='https://twitter.com/bowenNstuff') Justin
|
||||
.popover-content
|
||||
p <span class='label label-info'>$5 USD</span> will:
|
||||
ul.modal-indented-list
|
||||
li Add 20 gems to your account, which are used to buy special items.
|
||||
li Disable ads.
|
||||
li Donate to the developers (as an open source project, we can use all the help we can get).
|
||||
br
|
||||
.row-fluid
|
||||
.span6.well
|
||||
h3 Pay with Card
|
||||
.btn.btn-primary(ng-click='showStripe()') Pay with Card
|
||||
.span6.well
|
||||
h3 Pay with PayPal
|
||||
script(src='/bower_components/JavaScriptButtons/dist/paypal-button.min.js?merchant=#{env.PAYPAL_MERCHANT}', data-button='buynow', data-name='20 Gems, Disable Ads, Donation to the Developers', data-quantity='1', data-amount='5', data-currency='USD', data-tax='0', data-callback='#{env.BASE_URL}/api/v1/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')
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-default.cancel(ng-click='modals.buyGems = false') Cancel
|
||||
|
|
@ -3,7 +3,7 @@ include ./login
|
|||
include ./reroll
|
||||
include ./death
|
||||
include ./new-stuff
|
||||
include ./why-ads
|
||||
include ./more-gems
|
||||
include ./buy-gems
|
||||
include ./members
|
||||
include ./settings
|
||||
include ./settings
|
||||
include ./pets
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
div(modal='modals.moreGems', header='Out Of Gems')
|
||||
.modal-header
|
||||
h3 Out Of Gems
|
||||
.modal-body
|
||||
include ../gems
|
||||
p.
|
||||
Oops, out of Gems, which are used to buy special items! Habit is an open source project, and can use all the help
|
||||
it can get - buy more Gems to receive this pet, and consider it a donation to the contributors.
|
||||
.modal-footer
|
||||
button.btn.btn-default.cancel(ng-click='modals.moreGems = false') Cancel
|
||||
|
|
@ -1,49 +1,3 @@
|
|||
div(modal='user.flags.rewrite !== false')
|
||||
.modal-header
|
||||
h3 Mega Ultra Update!
|
||||
.modal-body
|
||||
table
|
||||
tr
|
||||
td
|
||||
.NPC-Bailey
|
||||
td
|
||||
.popover.static-popover.fade.right.in.wide-popover
|
||||
.arrow
|
||||
h3.popover-title
|
||||
a(target='_blank', href='http://www.kickstarter.com/profile/mihakuu') Bailey
|
||||
.popover-content
|
||||
p.
|
||||
Hello my Habiteers! I have some amazing news to share with you, it's huge!
|
||||
Has Habit ever crashed for you? (Joke). Well we <u><a target='_blank' href="http://habitrpg.tumblr.com/post/59104876969/website-issues-what-were-doing">re-wrote the website</a></u> from the ground up
|
||||
to conquor those critical bugs once and for all (more from Tyler in a bit). If you haven't seen me for a while (due to a bug in the old site), be sure to catch up with me on the right side of the screen for any missed news. Importantly:
|
||||
.alert.alert-success(style='margin-bottom:5px').
|
||||
<a target='_blank' href='https://play.google.com/store/apps/details?id=com.ocdevel.habitrpg'>Android</a> & <a target='_blank' href='https://itunes.apple.com/us/app/habitrpg/id689569235?mt=8'>iOS</a> Apps are out!</u>
|
||||
p.
|
||||
They're open source, so help us make them awesome. As for the rewrite: not all features are yet ported, but don't worry - you're still getting drops and streak-bonuses in the background, even if you can't see them yet.
|
||||
We'll be working hard to bring in all the missing features. And if you're not already, be sure to follow our updates on <a href="http://habitrpg.tumblr.com/" target="_blank">Tumblr</a> (there are some fun member highlights recently). One more thing: if you are a Veteran of the old site, I have granted you a Veteran Wolf! Check your inventory :)
|
||||
|
||||
table(style='clear:both;')
|
||||
tr
|
||||
td
|
||||
.popover.static-popover.fade.left.in
|
||||
.arrow
|
||||
h3.popover-title
|
||||
a(target='_blank', href='https://twitter.com/lefnire') Tyler
|
||||
.popover-content
|
||||
p.
|
||||
JavaScript developers! To me! We must finish vanquishing the old site, as not all features have been ported.
|
||||
We rewrote Habit on <a target='_blank' href='http://angularjs.org/'>AngularJS</a> + <a target='_blank' href='http://expressjs.com/'>Express</a>.
|
||||
We desparately need your help porting <a href='http://goo.gl/jYWTwl' target='_blank'>the rest of the features</a>, and polishing off the bugs. <a target='_blank' href='https://github.com/lefnire/habitrpg/wiki/Contributing#website'>Read this guide</a> to getting started.
|
||||
Thanks everyone for all your support and patience!
|
||||
|
||||
td
|
||||
img.pull-right(src='/bower_components/habitrpg-shared/img/unprocessed/efbd21c4-82a1-11e2-8190-fbc609b5c58b.png', style='height:72px')
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-default.cancel(ng-click='user.flags.rewrite = false') Read Later
|
||||
button.btn.btn-warning.cancel(ng-click='set("flags.rewrite", false)') Dismiss
|
||||
|
||||
|
||||
div(modal='modals.newStuff')
|
||||
.modal-header
|
||||
h3 New Stuff!
|
||||
|
|
@ -56,34 +10,61 @@ div(modal='modals.newStuff')
|
|||
.popover.static-popover.fade.right.in.wide-popover
|
||||
.arrow
|
||||
h3.popover-title
|
||||
a(target='_blank', href='http://www.kickstarter.com/profile/mihakuu') Bailey
|
||||
a(target='_blank', href='https://twitter.com/Mihakuu') Bailey
|
||||
.popover-content
|
||||
strong 09/01/2013
|
||||
p.
|
||||
We <a target='_blank' href="http://habitrpg.tumblr.com/post/59104876969/website-issues-what-were-doing">re-wrote the website from the ground up</a>
|
||||
And in case you missed it, <a target='_blank' href='https://play.google.com/store/apps/details?id=com.ocdevel.habitrpg'>Android</a> & <a target='_blank' href='https://itunes.apple.com/us/app/habitrpg/id689569235?mt=8'>iOS</a> Apps are out!
|
||||
Both apps and the website are open source, and we desparately need your help porting the rest of the features, and polishing off the bugs. <a target='_blank' href='https://github.com/lefnire/habitrpg/wiki/Contributing#website'>Read this guide</a> to getting started.
|
||||
Each of your pull requests shall grant you a special <a href='https://github.com/lefnire/habitrpg/issues/946#issuecomment-18654292' target='_blank'>Contributor Gear piece</a>.
|
||||
strong 10/22/2013
|
||||
p
|
||||
ul.modal-indented-list
|
||||
li TRICK OR TREAT! It's Habit Halloween! Some of the NPCs have decorated for the occasion. Can you spot us?
|
||||
li Two gem-purchasable skin tones are now available! The Rainbow Skin Set is here to stay, but in honor of Halloween, we also have the LIMITED EDITION SPOOKY SKIN SET. You will only be able to purchase the Spooky Skin Set until November 10th, so if you want a monstrous avatar, now's the time to act!
|
||||
li Do note, skins won't work on mobile until the app is updated. We'll update Android ASAP, iPhone usually takes ~1wk to approve.
|
||||
|
||||
hr
|
||||
h3 Archive
|
||||
p
|
||||
h4 10/19/2013
|
||||
ul
|
||||
li.
|
||||
New custom skin colors are now available! Go check them out in the Profile section. Also, the new mobile update, 0.0.10, is now available to download! It includes the new skin tones and the ability to hide or show your helm, among other things.
|
||||
li.
|
||||
You can now sell un-wanted drops to Alex the Merchant. Trade those troves of eggs for gold!
|
||||
h4 09/01/2013
|
||||
ul
|
||||
li.
|
||||
We <a target='_blank' href="http://habitrpg.tumblr.com/post/59104876969/website-issues-what-were-doing">re-wrote the website from the ground up</a>
|
||||
And in case you missed it, <a target='_blank' href='https://play.google.com/store/apps/details?id=com.ocdevel.habitrpg'>Android</a> & <a target='_blank' href='https://itunes.apple.com/us/app/habitrpg/id689569235?mt=8'>iOS</a> Apps are out!
|
||||
Both apps and the website are open source, and we desparately need your help porting the rest of the features, and polishing off the bugs. <a target='_blank' href='https://github.com/lefnire/habitrpg/wiki/Contributing#website'>Read this guide</a> to getting started.
|
||||
We're working on a system of Contributor Gear to reward the awesome people who help out, so stay tuned!
|
||||
|
||||
h4 The Rewrite! (Mid August)
|
||||
ul
|
||||
li.
|
||||
Hello my Habiteers! I have some amazing news to share with you, it's huge!
|
||||
Has Habit ever crashed for you? (Joke). Well we <u><a target='_blank' href="http://habitrpg.tumblr.com/post/59104876969/website-issues-what-were-doing">re-wrote the website</a></u> from the ground up
|
||||
to conquor those critical bugs once and for all (more from Tyler in a bit). If you haven't seen me for a while (due to a bug in the old site), be sure to catch up with me on the right side of the screen for any missed news. Importantly:
|
||||
.alert.alert-success(style='margin-bottom:5px').
|
||||
<a target='_blank' href='https://play.google.com/store/apps/details?id=com.ocdevel.habitrpg'>Android</a> & <a target='_blank' href='https://itunes.apple.com/us/app/habitrpg/id689569235?mt=8'>iOS</a> Apps are out!</u>
|
||||
li.
|
||||
They're open source, so help us make them awesome. As for the rewrite: not all features are yet ported, but don't worry - you're still getting drops and streak-bonuses in the background, even if you can't see them yet.
|
||||
We'll be working hard to bring in all the missing features. And if you're not already, be sure to follow our updates on <a href="http://habitrpg.tumblr.com/" target="_blank">Tumblr</a> (there are some fun member highlights recently). One more thing: if you are a Veteran of the old site, I have granted you a Veteran Wolf! Check your inventory :)
|
||||
li.
|
||||
JavaScript developers! To me! We must finish vanquishing the old site, as not all features have been ported.
|
||||
We rewrote Habit on <a target='_blank' href='http://angularjs.org/'>AngularJS</a> + <a target='_blank' href='http://expressjs.com/'>Express</a>.
|
||||
We desparately need your help porting <a href='http://goo.gl/jYWTwl' target='_blank'>the rest of the features</a>, and polishing off the bugs. <a target='_blank' href='https://github.com/lefnire/habitrpg/wiki/Contributing#website'>Read this guide</a> to getting started.
|
||||
Thanks everyone for all your support and patience!
|
||||
|
||||
h4 8/20/2013
|
||||
ul
|
||||
li
|
||||
| Timezone + custom day start issues fixed, your dailies should now reset properly and in your own timezone. (This was vexing
|
||||
strong Android
|
||||
| users particularly). If you're still experiencing issues,
|
||||
a(target='_blank', href='https://github.com/HabitRPG/habitrpg-mobile/issues/73#issuecomment-22960877') chime in here.
|
||||
li.
|
||||
Timezone + custom day start issues fixed, your dailies should now reset properly and in your own timezone. (This was vexing <strong>Android</strong> users particularly). If you're still experiencing issues, <a target='_blank' href='https://github.com/HabitRPG/habitrpg-mobile/issues/73#issuecomment-22960877'>chime in here</a>.
|
||||
li
|
||||
| API developers, the above means that
|
||||
strong cron
|
||||
| is automatically run for your users! Weee, they no longer have to log into the website to reset their dailies!
|
||||
h4 8/18/2013
|
||||
ul
|
||||
li
|
||||
a(target='_blank', href='https://play.google.com/store/apps/details?id=com.ocdevel.habitrpg') Android Mobile App is out!
|
||||
| There's a bug with Android 2.3, but we're working to fix it now :) The iPhone App has been submitted, so we're just waiting on Apple to approve it, which could take a week or two. We'll let you know as soon as they release it! For more details,
|
||||
a(target='_blank', href='http://habitrpg.tumblr.com/post/58449057415/android-mobile-app-released-iphone-app-coming-soon') see our Tumblr post
|
||||
li.
|
||||
The Mobile Apps are out! <a target='_blank' href="https://itunes.apple.com/us/app/habitrpg/id689569235">iOS app</a> and <a target='_blank' href="https://play.google.com/store/apps/details?id=com.ocdevel.habitrpg">Android</a>. There's a bug with Android 2.3, <a target='_blank' href='https://github.com/HabitRPG/habitrpg-mobile/issues/85'>follow the progress here</a>. For more details, see our <a target=_blank href="http://habitrpg.tumblr.com/post/58449057415/android-mobile-app-released-iphone-app-coming-soon">Tumblr post</a>
|
||||
li
|
||||
| Hey guys! Long time no see :) We want to make sure you guys have a better idea of what's going on behind the scenes, so we're going to be releasing
|
||||
b weekly status reports
|
||||
|
|
@ -154,7 +135,7 @@ div(modal='modals.newStuff')
|
|||
| (basic implementation, more to come)
|
||||
li
|
||||
| NPCs!
|
||||
a(target='_blank', href='http://www.kickstarter.com/profile/mihakuu') Bailey
|
||||
a(target='_blank', href='https://twitter.com/Mihakuu') Bailey
|
||||
| the Town Crier,
|
||||
a(target='_blank', href='http://www.kickstarter.com/profile/523661924') Alexander
|
||||
| the
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
div(modal='modals.pets.dropsEnabled')
|
||||
div(modal='modals.dropsEnabled')
|
||||
.modal-header
|
||||
h3 Drops Enabled!
|
||||
.modal-body
|
||||
p
|
||||
span.item-drop-icon(ng-class='Pet_Egg_{{user.items.eggs.0.name}}')
|
||||
| You've unlocked the Drop System! Now when you complete tasks, you have a small chance of finding an item. And guess what, you just found a
|
||||
strong {{user.items.eggs.0.text}} egg
|
||||
| ! {{user.items.eggs.0.notes}}
|
||||
span.item-drop-icon(class='Pet_Egg_{{user.items.eggs.0.name}}', style='margin-left: 0px')
|
||||
| You've unlocked the Drop System! Now when you complete tasks, you have a small chance of finding an item. You just found a
|
||||
strong {{user.items.eggs.0.text}} Egg
|
||||
| ! {{user.items.eggs.0.notes}}.
|
||||
br
|
||||
p.
|
||||
<span class='Pet_Currency_Gem item-drop-icon'></span> If you've got your eye on a pet, but can't wait any longer for it to drop, use Gems in <strong>Options > Inventory</strong> to buy one!
|
||||
.modal-footer
|
||||
button.btn.btn-default.cancel(ng-click='modals.pets.dropsEnabled=false') Close
|
||||
button.btn.btn-default.cancel(ng-click='modals.dropsEnabled = false') Close
|
||||
|
||||
div(modal='modals.pets.itemDropped')
|
||||
div(modal='modals.drop')
|
||||
.modal-header
|
||||
h3 An item has dropped!
|
||||
.modal-body
|
||||
p
|
||||
span.item-drop-icon(ng-class='Pet_{{_drop.type}}_{{_drop.name}}')
|
||||
| {{_drop.dialog}}
|
||||
span.item-drop-icon(class='Pet_{{user._tmp.drop.type}}_{{user._tmp.drop.name}}')
|
||||
| {{user._tmp.drop.dialog}}
|
||||
.modal-footer
|
||||
button.btn.btn-default.cancel(ng-click='modals.pets.itemDropped=false') Close
|
||||
button.btn.btn-default.cancel(ng-click='modals.drop = false') Close
|
||||
|
|
@ -11,7 +11,7 @@ div(modal='modals.reroll')
|
|||
|
||||
.modal-footer
|
||||
span(ng-if='user.balance < 1')
|
||||
a.btn.btn-success.btn-large(ng-click="showStripe()") Buy More Gems
|
||||
a.btn.btn-success.btn-large(ng-click="modals.buyGems = true") Buy More Gems
|
||||
span.gem-cost Not enough Gems
|
||||
span(ng-if='user.balance >= 1', ng-controller='SettingsCtrl')
|
||||
a.btn.btn-danger.btn-large(ng-click='reroll()') Re-Roll
|
||||
|
|
|
|||
|
|
@ -18,29 +18,29 @@ div(ng-controller='SettingsCtrl')
|
|||
form#restore-form.form-horizontal
|
||||
h3 Stats
|
||||
.option-group.option-medium
|
||||
input.option-content(type='number', data-for='stats.hp', ng-model='user.stats.hp')
|
||||
input.option-content(type='number', step="any", data-for='stats.hp', ng-model='restoreValues.stats.hp')
|
||||
span.input-suffix HP
|
||||
.option-group.option-medium
|
||||
input.option-content(type='number', data-for='stats.exp', ng-model='user.stats.exp')
|
||||
input.option-content(type='number', step="any", data-for='stats.exp', ng-model='restoreValues.stats.exp')
|
||||
span.input-suffix Exp
|
||||
.option-group.option-medium
|
||||
input.option-content(type='number', data-for='stats.gp', ng-model='user.stats.gp')
|
||||
input.option-content(type='number', step="any", data-for='stats.gp', ng-model='restoreValues.stats.gp')
|
||||
span.input-suffix GP
|
||||
.option-group.option-medium
|
||||
input.option-content(type='number', data-for='stats.lvl', ng-model='user.stats.lvl')
|
||||
input.option-content(type='number', data-for='stats.lvl', ng-model='restoreValues.stats.lvl')
|
||||
span.input-suffix Level
|
||||
h3 Items
|
||||
.option-group.option-medium
|
||||
input.option-content(type='number', data-for='items.weapon', ng-model='user.items.weapon')
|
||||
input.option-content(type='number', data-for='items.weapon', ng-model='restoreValues.items.weapon')
|
||||
span.input-suffix Weapon
|
||||
.option-group.option-medium
|
||||
input.option-content(type='number', data-for='items.armor', ng-model='user.items.armor')
|
||||
input.option-content(type='number', data-for='items.armor', ng-model='restoreValues.items.armor')
|
||||
span.input-suffix Armor
|
||||
.option-group.option-medium
|
||||
input.option-content(type='number', data-for='items.head', ng-model='user.items.head')
|
||||
input.option-content(type='number', data-for='items.head', ng-model='restoreValues.items.head')
|
||||
span.input-suffix Helm
|
||||
.option-group.option-medium
|
||||
input.option-content(type='number', data-for='items.shield', ng-model='user.items.shield')
|
||||
input.option-content(type='number', data-for='items.shield', ng-model='restoreValues.items.shield')
|
||||
span.input-suffix Shield
|
||||
.modal-footer
|
||||
button.btn.btn-primary(ng-click='restore()') Save & Close
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
div(modal='modals.whyAds')
|
||||
.modal-header
|
||||
h3 Why Ads?
|
||||
.modal-body
|
||||
p.
|
||||
Habit is an open source project, and can use all the help it can get - consider this a donation to the contributors. You also get 20 Gems from the purchase, which you can use to buy special items.
|
||||
p
|
||||
| "Hey, I backed the Kickstarter!" - follow
|
||||
a(target='_blank', href='http://community.habitrpg.com/node/22') these instructions.
|
||||
.modal-footer
|
||||
button.btn.btn-default.cancel(ng-click='modals.whyAds = false') Ok
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
Helped HabitRPG grow by filling out <a href='http://community.habitrpg.com/node/290' target='_blank'>this survey.</a>
|
||||
hr
|
||||
|
||||
div(ng-if='profile.achievements.originalUser || profile.achievements.veteranUser')
|
||||
div(ng-if='profile.achievements.originalUser || profile.achievements.veteran')
|
||||
.achievement.achievement-cake
|
||||
div(ng-if='profile.achievements.veteran')
|
||||
h5 Veteran
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ block content
|
|||
a.btn.btn-primary.btn-small(ng-click='playButtonClick()') Play
|
||||
.container
|
||||
p(style='height:600;')
|
||||
iframe(src='//player.vimeo.com/video/57639356', width='100%', height='539', frameborder='0', webkitallowfullscreen='', mozallowfullscreen='', allowfullscreen='')
|
||||
iframe(src='//player.vimeo.com/video/76557040', width='100%', height='539', frameborder='0', webkitallowfullscreen='', mozallowfullscreen='', allowfullscreen='')
|
||||
|
||||
.modal.fade#login-modal(style='display:none')
|
||||
.modal-dialog
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ html
|
|||
script(type='text/javascript', src='/bower_components/bootstrap/docs/assets/js/bootstrap.min.js')
|
||||
|
||||
script(type='text/javascript', src='/js/static.js')
|
||||
script(type='text/javascript', src='/js/services/memberServices.js')
|
||||
script(type='text/javascript', src='/js/services/userServices.js')
|
||||
script(type='text/javascript', src='/js/controllers/authCtrl.js')
|
||||
-}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
div(ng-if='authenticated() && user.flags.ads!="hide"')
|
||||
div(ng-if='authenticated() && !user.purchased.ads')
|
||||
span.pull-right(ng-if='list.type!="reward"')
|
||||
a(ng-click='showStripe()', tooltip='Remove Ads')
|
||||
a(ng-click='modals.buyGems=true', tooltip='Remove Ads')
|
||||
i.icon-remove
|
||||
br
|
||||
a(ng-click='modals.whyAds=true', tooltip='Why Ads?')
|
||||
i.icon-question-sign
|
||||
|
||||
div(ng-if='list.type=="habit"', habitrpg-adsense)
|
||||
script(async='async', src='//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js')
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ div(ng-controller='TasksCtrl')
|
|||
|
||||
// Todos export/graph options
|
||||
span.option-box.pull-right(ng-if='list.main && list.type=="todo"')
|
||||
a.option-action(ng-show='user.history.todos', x-bind='click:toggleChart', ng-click='notPorted()', data-id='todos', tooltip='Progress')
|
||||
a.option-action(ng-show='user.history.todos', ng-click='toggleChart("todos")', tooltip='Progress')
|
||||
i.icon-signal
|
||||
//-a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{user.apiToken}}', tooltip='iCal')
|
||||
a.option-action(ng-click='notPorted()', tooltip='iCal')
|
||||
a.option-action(ng-click='notPorted()', tooltip='iCal', ng-show='false')
|
||||
i.icon-calendar
|
||||
// <a href="https://www.google.com/calendar/render?cid={{encodeiCalLink(_user.id, _user.apiToken)}}" rel=tooltip title="Google Calendar"><i class=icon-calendar></i></a>
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ div(ng-controller='TasksCtrl')
|
|||
h2.task-column_title {{list.header}}
|
||||
|
||||
// Todo Chart
|
||||
.todos-chart(ng-if='list.type == "todo"', ng-show='_page.charts.todos')
|
||||
.todos-chart(ng-if='list.type == "todo"', ng-show='charts.todos')
|
||||
|
||||
// Add New
|
||||
form.addtask-form.form-inline.new-task-form(name='new{{list.type}}form', ng-show='list.editable', ng-hide='list.showCompleted && list.type=="todo"', data-task-type='{{list.type}}', ng-submit='addTask(list)')
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use
|
|||
i.icon-tags(tooltip='{{appliedTags(user.tags, task.tags)}}', ng-hide='noTags(task.tags)')
|
||||
|
||||
// edit
|
||||
a(ng-click='task._editing = !task._editing', tooltip='Edit')
|
||||
i.icon-pencil
|
||||
a(ng-hide='task._editing', ng-click='toggleEdit(task)', tooltip='Edit')
|
||||
i.icon-pencil(ng-hide='task._editing')
|
||||
// cancel
|
||||
a(ng-hide='!task._editing', ng-click='toggleEdit(task)', tooltip='Cancel')
|
||||
i.icon-remove(ng-hide='!task._editing')
|
||||
//- challenges
|
||||
// {{#if task.challenge}}
|
||||
// {{#if brokenChallengeLink(task)}}
|
||||
|
|
@ -27,10 +30,10 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use
|
|||
a(ng-click='remove(task)', tooltip='Delete')
|
||||
i.icon-trash
|
||||
// chart
|
||||
a(ng-show='task.history', x-bind='click:toggleChart', ng-click='notPorted()', data-id='{{task.id}}', tooltip='Progress')
|
||||
a(ng-show='task.history', ng-click='toggleChart(task.id, task)', tooltip='Progress')
|
||||
i.icon-signal
|
||||
// notes
|
||||
span.task-notes(ng-show='task.notes', popover-trigger='mouseenter', popover-placement='left', popover='{{task.notes}}', popover-title='{{task.text}}')
|
||||
span.task-notes(ng-show='task.notes && !task._editing', popover-trigger='mouseenter', popover-placement='left', popover='{{task.notes}}', popover-title='{{task.text}}')
|
||||
i.icon-comment
|
||||
|
||||
// left-hand side checkbox
|
||||
|
|
@ -39,8 +42,8 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use
|
|||
// Habits
|
||||
span(ng-if='list.main && task.type=="habit"')
|
||||
// only allow scoring on main tasks, not when viewing others' public tasks or when creating challenges
|
||||
a.task-action-btn(ng-show='task.up', ng-click='score(task,"up")') +
|
||||
a.task-action-btn(ng-show='task.down', ng-click='score(task,"down")') -
|
||||
a.task-action-btn(ng-if='task.up', ng-click='score(task,"up")') +
|
||||
a.task-action-btn(ng-if='task.down', ng-click='score(task,"down")') -
|
||||
//span(ng-if='!list.main')
|
||||
// span.task-action-btn(ng-show='task.up') +
|
||||
// span.task-action-btn(ng-show='task.down') =
|
||||
|
|
@ -86,7 +89,7 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use
|
|||
fieldset.option-group
|
||||
// {{#unless taskInChallenge(task)}}
|
||||
label.option-title Text
|
||||
input.option-content(type='text', ng-model='task.text')
|
||||
input.option-content(type='text', ng-model='task.text', required)
|
||||
// {{/}}
|
||||
label.option-title Extra Notes
|
||||
// {{#if taskInChallenge(task)}}
|
||||
|
|
@ -120,7 +123,7 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use
|
|||
// if Reward, pricing
|
||||
fieldset.option-group.option-short(ng-if='task.type=="reward"')
|
||||
legend.option-title Price
|
||||
input.option-content(type='number', size='16', min='0', ng-model='task.value')
|
||||
input.option-content(type='number', size='16', min='0', step="any", ng-model='task.value')
|
||||
.money.input-suffix
|
||||
span.shop_gold
|
||||
// if Todos, the due date
|
||||
|
|
@ -157,4 +160,4 @@ li(ng-repeat='task in user[list.type + "s"]', class='task {{taskClasses(task,use
|
|||
input.option-content(type='number', ng-model='task.streak')
|
||||
button.task-action-btn.tile.spacious(type='submit') Save & Close
|
||||
|
||||
div(class='{{task.id}}-chart', ng-show='_page.charts[task.id]')
|
||||
div(class='{{task.id}}-chart', ng-show='charts[task.id]')
|
||||
|
|
|
|||
Loading…
Reference in a new issue