Merge branch 'develop' into card_tweaks

This commit is contained in:
Blade Barringer
2015-08-14 17:29:17 -05:00
74 changed files with 725 additions and 511 deletions
+6 -3
View File
@@ -1,7 +1,7 @@
"use strict";
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Shared', 'Guide', 'Tasks', 'Analytics',
function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide, Tasks, Analytics) {
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Content', 'Shared', 'Guide', 'Tasks', 'Analytics',
function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Content, Shared, Guide, Tasks, Analytics) {
$scope.obj = User.user; // used for task-lists
$scope.user = User.user;
@@ -212,10 +212,13 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
------------------------
*/
$scope.$watchGroup(['user.items.gear.owned', 'user.flags.armoireEnabled'], function(){
$scope.$watch('user.items.gear.owned', function(){
$scope.itemStore = Shared.updateStore(User.user);
},true);
$scope.healthPotion = Content.potion;
$scope.armoire = Content.armoire;
$scope.buy = function(item) {
User.user.ops.buy({params:{key:item.key}});
$rootScope.playSound('Reward');
@@ -108,6 +108,12 @@
properties.Mana = Math.floor(user.stats.mp);
}
properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2;
properties["Number Of Tasks"] = {
habits: user.habits.length,
dailys: user.dailys.length,
todos: user.todos.length,
rewards: user.rewards.length
};
if (user.contributor && user.contributor.level) properties.contributorLevel = user.contributor.level;
if (user.purchased && user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId;
}
+6
View File
@@ -153,6 +153,12 @@ function _formatUserData(user) {
}
properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2;
properties["Number Of Tasks"] = {
habits: user.habits.length,
dailys: user.dailys.length,
todos: user.todos.length,
rewards: user.rewards.length
};
if (user.contributor && user.contributor.level) {
properties.contributorLevel = user.contributor.level;
+2 -2
View File
@@ -102,13 +102,13 @@ dataexport.avatarPage = function(req, res) {
User.findById(req.params.uuid).select('stats profile items achievements preferences backer contributor').exec(function(err, user){
res.render('avatar-static', {
title: user.profile.name,
env: _.defaults({user:user},res.locals.habitrpg)
env: _.defaults({user:user}, res.locals.habitrpg)
});
})
};
dataexport.avatarImage = function(req, res, next) {
var filename = 'avatar-'+req.params.uuid+'.png';
var filename = 'avatars/'+req.params.uuid+'.png';
request.head('https://'+bucket+'.s3.amazonaws.com/'+filename, function(err,response,body) {
// cache images for 10 minutes on aws, else upload a new one
if (response.statusCode==200 && moment().diff(response.headers['last-modified'], 'minutes') < 10)
+1 -1
View File
@@ -148,7 +148,7 @@ module.exports = {
// Export en strings only, temporary solution for mobile
// This is copied from middleware.js#module.exports.locals#t()
// This is copied from middlewares/locals#t()
module.exports.enTranslations = function(){ // stringName and vars are the allowed parameters
var language = _.find(avalaibleLanguages, {code: 'en'});
//language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
+59
View File
@@ -0,0 +1,59 @@
var fs = require('fs');
var path = require('path');
var nconf = require('nconf');
var _ = require('lodash');
var manifestFiles = require("../../public/manifest.json");
var IS_PROD = nconf.get('NODE_ENV') === 'production';
var buildFiles = [];
var walk = function(folder){
var res = fs.readdirSync(folder);
res.forEach(function(fileName){
var file = folder + '/' + fileName;
if(fs.statSync(file).isDirectory()){
walk(file);
}else{
var relFolder = path.relative(path.join(__dirname, "/../../build"), folder);
var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1');
if(relFolder){
old = relFolder + '/' + old;
fileName = relFolder + '/' + fileName;
}
buildFiles[old] = fileName;
}
});
};
walk(path.join(__dirname, "/../../build"));
var getBuildUrl = module.exports.getBuildUrl = function(url){
if(buildFiles[url]) return '/' + buildFiles[url];
return '/' + url;
};
module.exports.getManifestFiles = function(page){
var files = manifestFiles[page];
if(!files) throw new Error("Page not found!");
var code = '';
if(IS_PROD){
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(page + '.css') + '">';
code += '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
}else{
_.each(files.css, function(file){
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
});
_.each(files.js, function(file){
code += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
});
}
return code;
};
-249
View File
@@ -1,249 +0,0 @@
var nconf = require('nconf');
var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var User = require('./models/user').model
var limiter = require('connect-ratelimit');
var logging = require('./logging');
var domainMiddleware = require('domain-middleware');
var cluster = require('cluster');
var i18n = require('./i18n.js');
var shared = require('../../common');
var request = require('request');
var os = require('os');
var moment = require('moment');
var utils = require('./utils');
var IS_PROD = nconf.get('NODE_ENV') === 'production';
var BASE_URL = nconf.get("BASE_URL");
module.exports.apiThrottle = function(app) {
if (!IS_PROD) return;
app.use(limiter({
end:false,
catagories:{
normal: {
// 2 req/s, but split as minutes
totalRequests: 80,
every: 60000
}
}
})).use(function(req,res,next){
//logging.info(res.ratelimit);
if (res.ratelimit.exceeded) return res.json(429,{err:'Rate limit exceeded'});
next();
});
}
module.exports.domainMiddleware = function(server,mongoose) {
if (IS_PROD) {
var mins = 3, // how often to run this check
useAvg = false, // use average over 3 minutes, or simply the last minute's report
url = 'https://api.newrelic.com/v2/applications/'+nconf.get('NEW_RELIC_APPLICATION_ID')+'/metrics/data.json?names[]=Apdex&values[]=score';
setInterval(function(){
// see https://docs.newrelic.com/docs/apm/apis/api-v2-examples/average-response-time-examples-api-v2, https://rpm.newrelic.com/api/explore/applications/data
request({
url: useAvg ? url+'&from='+moment().subtract({minutes:mins}).utc().format()+'&to='+moment().utc().format()+'&summarize=true' : url,
headers: {'X-Api-Key': nconf.get('NEW_RELIC_API_KEY')}
}, function(err, response, body){
var ts = JSON.parse(body).metric_data.metrics[0].timeslices,
score = ts[ts.length-1].values.score,
apdexBad = score < .75 || score == 1,
memory = os.freemem() / os.totalmem(),
memoryHigh = memory < 0.1;
if (/*apdexBad || */memoryHigh) throw "[Memory Leak] Apdex="+score+" Memory="+parseFloat(memory).toFixed(3)+" Time="+moment().format();
})
}, mins*60*1000);
}
return domainMiddleware({
server: {
close:function(){
server.close();
mongoose.connection.close();
}
},
killTimeout: 10000
});
}
module.exports.errorHandler = function(err, req, res, next) {
//res.locals.domain.emit('error', err);
// when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER)
var stack = (err.stack ? err.stack : err.message ? err.message : err) +
"\n ----------------------------\n" +
"\n\noriginalUrl: " + req.originalUrl +
"\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] +
"\n\nheaders: " + JSON.stringify(req.headers) +
"\n\nbody: " + JSON.stringify(req.body) +
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
logging.error(stack);
/*logging.loggly({
error: "Uncaught error",
stack: (err.stack || err.message || err),
body: req.body, headers: req.header,
auth: req.headers['x-api-user'],
originalUrl: req.originalUrl
});*/
var message = err.message ? err.message : err;
message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length);
res.json(500,{err:message}); //res.end(err.message);
}
function isHTTP(req) {
return (
req.headers['x-forwarded-proto'] &&
req.headers['x-forwarded-proto'] !== 'https' &&
IS_PROD &&
BASE_URL.indexOf('https') === 0
);
}
function isProxied(req) {
return (
req.headers['x-habitica-lb'] &&
req.headers['x-habitica-lb'] === 'Yes'
);
}
module.exports.forceSSL = function(req, res, next){
if(isHTTP(req) && !isProxied(req)) {
return res.redirect(BASE_URL + req.url);
}
next();
}
// Redirect to habitica for non-api urls
// NOTE: Currently using a static 'habitica.com' string, rather than BASE_URL,
// to make rollback easy. Eventually, BASE_URL should be migrated.
function nonApiUrl(req) {
return req.url.search(/\/api\//) === -1;
}
module.exports.forceHabitica = function(req, res, next) {
var ignoreRedirect = nconf.get('IGNORE_REDIRECT');
if (IS_PROD && !ignoreRedirect && !isProxied(req) && nonApiUrl(req)) {
return res.redirect('https://habitica.com' + req.url);
}
next();
};
module.exports.cors = function(req, res, next) {
res.header("Access-Control-Allow-Origin", req.headers.origin || "*");
res.header("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,HEAD,DELETE");
res.header("Access-Control-Allow-Headers", "Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key");
if (req.method === 'OPTIONS') return res.send(200);
return next();
};
var siteVersion = 1;
module.exports.forceRefresh = function(req, res, next){
if(req.query.siteVersion && req.query.siteVersion != siteVersion){
return res.json(400, {needRefresh: true});
}
return next();
};
var buildFiles = [];
var walk = function(folder){
var res = fs.readdirSync(folder);
res.forEach(function(fileName){
file = folder + '/' + fileName;
if(fs.statSync(file).isDirectory()){
walk(file);
}else{
var relFolder = path.relative(path.join(__dirname, "/../build"), folder);
var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1');
if(relFolder){
old = relFolder + '/' + old;
fileName = relFolder + '/' + fileName;
}
buildFiles[old] = fileName
}
});
}
walk(path.join(__dirname, "/../build"));
var getBuildUrl = function(url){
if(buildFiles[url]) return '/' + buildFiles[url];
return '/' + url;
}
var manifestFiles = require("../public/manifest.json");
var getManifestFiles = function(page){
var files = manifestFiles[page];
if(!files) throw new Error("Page not found!");
var code = '';
if(IS_PROD){
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(page + '.css') + '">';
code += '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
}else{
_.each(files.css, function(file){
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
});
_.each(files.js, function(file){
code += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
});
}
return code;
}
module.exports.locals = function(req, res, next) {
var language = _.find(i18n.avalaibleLanguages, {code: req.language});
var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/'
// Load moment.js language file only when not on static pages
language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
var tavern = require('./models/group').tavern;
var envVars = _.pick(nconf.get(), 'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' '));
res.locals.habitrpg = _.merge(envVars, {
IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
getManifestFiles: getManifestFiles,
getBuildUrl: getBuildUrl,
avalaibleLanguages: i18n.avalaibleLanguages,
language: language,
isStaticPage: isStaticPage,
translations: i18n.translations[language.code],
t: function(){ // stringName and vars are the allowed parameters
var args = Array.prototype.slice.call(arguments, 0);
args.push(language.code);
return shared.i18n.t.apply(null, args);
},
siteVersion: siteVersion,
Content: shared.content,
mods: require('./models/user').mods,
tavern: tavern, // for world boss
worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {},
_: _,
MP_ID: nconf.get('MP_ID'),
AMAZON_PAYMENTS: {
SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'),
CLIENT_ID: nconf.get('AMAZON_PAYMENTS:CLIENT_ID')
}
});
// Put query-string party (& guild but use partyInvite for backward compatibility)
// invitations into session to be handled later
try{
req.session.partyInvite = JSON.parse(utils.decrypt(req.query.partyInvite))
} catch(e){}
next();
}
+22
View File
@@ -0,0 +1,22 @@
var nconf = require('nconf');
var limiter = require('connect-ratelimit');
var IS_PROD = nconf.get('NODE_ENV') === 'production';
module.exports = function(app) {
if (!IS_PROD) return;
app.use(limiter({
end:false,
categories:{
normal: {
// 2 req/s, but split as minutes
totalRequests: 80,
every: 60000
}
}
})).use(function(req,res,next){
//logging.info(res.ratelimit);
if (res.ratelimit.exceeded) return res.json(429,{err:'Rate limit exceeded'});
next();
});
};
+7
View File
@@ -0,0 +1,7 @@
module.exports = function(req, res, next) {
res.header("Access-Control-Allow-Origin", req.headers.origin || "*");
res.header("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,HEAD,DELETE");
res.header("Access-Control-Allow-Headers", "Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key");
if (req.method === 'OPTIONS') return res.send(200);
return next();
};
+39
View File
@@ -0,0 +1,39 @@
var nconf = require('nconf');
var moment = require('moment');
var domainMiddleware = require('domain-middleware');
var os = require('os');
var request = require('request');
var IS_PROD = nconf.get('NODE_ENV') === 'production';
module.exports = function(server,mongoose) {
if (IS_PROD) {
var mins = 3, // how often to run this check
useAvg = false, // use average over 3 minutes, or simply the last minute's report
url = 'https://api.newrelic.com/v2/applications/'+nconf.get('NEW_RELIC_APPLICATION_ID')+'/metrics/data.json?names[]=Apdex&values[]=score';
setInterval(function(){
// see https://docs.newrelic.com/docs/apm/apis/api-v2-examples/average-response-time-examples-api-v2, https://rpm.newrelic.com/api/explore/applications/data
request({
url: useAvg ? url+'&from='+moment().subtract({minutes:mins}).utc().format()+'&to='+moment().utc().format()+'&summarize=true' : url,
headers: {'X-Api-Key': nconf.get('NEW_RELIC_API_KEY')}
}, function(err, response, body){
var ts = JSON.parse(body).metric_data.metrics[0].timeslices,
score = ts[ts.length-1].values.score,
apdexBad = score < .75 || score == 1,
memory = os.freemem() / os.totalmem(),
memoryHigh = memory < 0.1;
if (/*apdexBad || */memoryHigh) throw "[Memory Leak] Apdex="+score+" Memory="+parseFloat(memory).toFixed(3)+" Time="+moment().format();
});
}, mins*60*1000);
}
return domainMiddleware({
server: {
close:function(){
server.close();
mongoose.connection.close();
}
},
killTimeout: 10000
});
};
+24
View File
@@ -0,0 +1,24 @@
var logging = require('../logging');
module.exports = function(err, req, res, next) {
//res.locals.domain.emit('error', err);
// when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER)
var stack = (err.stack ? err.stack : err.message ? err.message : err) +
"\n ----------------------------\n" +
"\n\noriginalUrl: " + req.originalUrl +
"\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] +
"\n\nheaders: " + JSON.stringify(req.headers) +
"\n\nbody: " + JSON.stringify(req.body) +
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
logging.error(stack);
/*logging.loggly({
error: "Uncaught error",
stack: (err.stack || err.message || err),
body: req.body, headers: req.header,
auth: req.headers['x-api-user'],
originalUrl: req.originalUrl
});*/
var message = err.message ? err.message : err;
message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length);
res.json(500,{err:message}); //res.end(err.message);
};
+9
View File
@@ -0,0 +1,9 @@
module.exports.siteVersion = 1;
module.exports.middleware = function(req, res, next){
if(req.query.siteVersion && req.query.siteVersion != module.exports.siteVersion){
return res.json(400, {needRefresh: true});
}
return next();
};
+71
View File
@@ -0,0 +1,71 @@
var nconf = require('nconf');
var _ = require('lodash');
var utils = require('../utils');
var shared = require('../../../common');
var i18n = require('../i18n.js');
var buildManifest = require('../libs/buildManifest');
var shared = require('../../../common');
var forceRefresh = require('./forceRefresh');
var tavern = require('../models/group').tavern;
var mods = require('../models/user').mods;
// To avoid stringifying more data then we need,
// items from `env` used on the client will have to be specified in this array
var clientVars = ['language', 'isStaticPage', 'avalaibleLanguages', 'translations',
'FACEBOOK_KEY', 'NODE_ENV', 'BASE_URL', 'GA_ID',
'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY',
'worldDmg', 'mods', 'IS_MOBILE'];
var env = {
getManifestFiles: buildManifest.getManifestFiles,
getBuildUrl: buildManifest.getBuildUrl,
_: _,
clientVars: clientVars,
tavern: tavern, // for world boss
mods: mods,
Content: shared.content,
siteVersion: forceRefresh.siteVersion,
avalaibleLanguages: i18n.avalaibleLanguages,
AMAZON_PAYMENTS: {
SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'),
CLIENT_ID: nconf.get('AMAZON_PAYMENTS:CLIENT_ID')
}
};
'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' ').forEach(function(key){
env[key] = nconf.get(key);
});
module.exports = function(req, res, next) {
var language = _.find(i18n.avalaibleLanguages, {code: req.language});
var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/'
// Load moment.js language file only when not on static pages
language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
res.locals.habitrpg = _.assign(env, {
IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
language: language,
isStaticPage: isStaticPage,
translations: i18n.translations[language.code],
t: function(){ // stringName and vars are the allowed parameters
var args = Array.prototype.slice.call(arguments, 0);
args.push(language.code);
return shared.i18n.t.apply(null, args);
},
// Defined here and not outside of the middleware because tavern might be an
// empty object until the query to fetch it finishes
worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {},
});
// Put query-string party (& guild but use partyInvite for backward compatibility)
// invitations into session to be handled later
// TODO once we have proper logging in place, log errors here
if(req.query.partyInvite){
try{
req.session.partyInvite = JSON.parse(utils.decrypt(req.query.partyInvite));
} catch(e){}
}
next();
};
+43
View File
@@ -0,0 +1,43 @@
var nconf = require('nconf');
var IS_PROD = nconf.get('NODE_ENV') === 'production';
var ignoreRedirect = nconf.get('IGNORE_REDIRECT');
var BASE_URL = nconf.get("BASE_URL");
function isHTTP(req) {
return (
req.headers['x-forwarded-proto'] &&
req.headers['x-forwarded-proto'] !== 'https' &&
IS_PROD &&
BASE_URL.indexOf('https') === 0
);
}
function isProxied(req) {
return (
req.headers['x-habitica-lb'] &&
req.headers['x-habitica-lb'] === 'Yes'
);
}
module.exports.forceSSL = function(req, res, next){
if(isHTTP(req) && !isProxied(req)) {
return res.redirect(BASE_URL + req.url);
}
next();
};
// Redirect to habitica for non-api urls
// NOTE: Currently using a static 'habitica.com' string, rather than BASE_URL,
// to make rollback easy. Eventually, BASE_URL should be migrated.
function nonApiUrl(req) {
return req.url.search(/\/api\//) === -1;
}
module.exports.forceHabitica = function(req, res, next) {
if (IS_PROD && !ignoreRedirect && !isProxied(req) && nonApiUrl(req)) {
return res.redirect('https://habitica.com' + req.url);
}
next();
};
+2 -1
View File
@@ -236,7 +236,8 @@ module.exports.tavern = {};
var tavernQ = {_id:'habitrpg','quest.key':{$ne:null}};
process.nextTick(function(){
mongoose.model('Group').findOne(tavernQ,function(err,tavern){
module.exports.tavern = tavern;
// Using _assign so we don't lose the reference to the exported tavern
_.assign(module.exports.tavern, tavern);
});
})
GroupSchema.statics.tavernBoss = function(user,progress) {
+6 -1
View File
@@ -577,11 +577,16 @@ UserSchema.methods.unlink = function(options, cb) {
module.exports.schema = UserSchema;
module.exports.model = mongoose.model("User", UserSchema);
// Initially export an empty object so external requires will get
// the right object by reference when it's defined later
// Otherwise it would remain undefined if requested before the query executes
module.exports.mods = [];
mongoose.model("User")
.find({'contributor.admin':true})
.sort('-contributor.level -backer.npc profile.name')
.select('profile contributor backer')
.exec(function(err,mods){
module.exports.mods = mods
// Using push to maintain the reference to mods
module.exports.mods.push.apply(module.exports.mods, mods);
});
+2 -2
View File
@@ -5,9 +5,9 @@ var async = require('async');
var icalendar = require('icalendar');
var api = require('./../controllers/user');
var auth = require('./../controllers/auth');
var middleware = require('../middleware');
var logging = require('./../logging');
var i18n = require('./../i18n');
var forceRefresh = require('../middlewares/forceRefresh').middleware;
/* ---------- Deprecated API ------------*/
@@ -161,7 +161,7 @@ router.post('/user/task', auth.auth, i18n.getUserLanguage, cron, api.addTask);
// User
router.get('/user', auth.auth, i18n.getUserLanguage, cron, api.getUser);
router.post('/user/revive', auth.auth, i18n.getUserLanguage, cron, api.revive);
router.post('/user/batch-update', middleware.forceRefresh, auth.auth, i18n.getUserLanguage, cron, batchUpdate);
router.post('/user/batch-update', forceRefresh, auth.auth, i18n.getUserLanguage, cron, batchUpdate);
function deprecated(req, res) {
res.json(404, {err:'API v1 is no longer supported, please use API v2 instead (https://github.com/HabitRPG/habitrpg/blob/develop/API.md)'});
+2 -3
View File
@@ -16,12 +16,11 @@ hall = require("../controllers/hall")
challenges = require("../controllers/challenges")
dataexport = require("../controllers/dataexport")
nconf = require("nconf")
middleware = require("../middleware")
cron = user.cron
_ = require('lodash')
content = require('../../../common').content
i18n = require('../i18n')
forceRefresh = require('../middlewares/forceRefresh').middleware
module.exports = (swagger, v2) ->
[path,body,query] = [swagger.pathParam, swagger.bodyParam, swagger.queryParam]
@@ -326,7 +325,7 @@ module.exports = (swagger, v2) ->
parameters:[
body '','The array of batch-operations to perform','object'
]
middleware: [middleware.forceRefresh, auth.auth, i18n.getUserLanguage, cron, user.sessionPartyInvite]
middleware: [forceRefresh, auth.auth, i18n.getUserLanguage, cron, user.sessionPartyInvite]
action: user.batchUpdate
# Tags
+3 -3
View File
@@ -4,13 +4,13 @@ var dataexport = require('../controllers/dataexport');
var auth = require('../controllers/auth');
var nconf = require('nconf');
var i18n = require('../i18n');
var middleware = require('../middleware.js');
var locals = require('../middlewares/locals');
/* Data export */
router.get('/history.csv',auth.authWithSession,i18n.getUserLanguage,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes
router.get('/userdata.xml',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.xml);
router.get('/userdata.json',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.json);
router.get('/avatar-:uuid.html', i18n.getUserLanguage, middleware.locals, dataexport.avatarPage);
router.get('/avatar-:uuid.png', i18n.getUserLanguage, middleware.locals, dataexport.avatarImage);
router.get('/avatar-:uuid.html', i18n.getUserLanguage, locals, dataexport.avatarPage);
router.get('/avatar-:uuid.png', i18n.getUserLanguage, locals, dataexport.avatarImage);
module.exports = router;
+3 -3
View File
@@ -2,13 +2,13 @@ var nconf = require('nconf');
var express = require('express');
var router = new express.Router();
var _ = require('lodash');
var middleware = require('../middleware');
var locals = require('../middlewares/locals');
var user = require('../controllers/user');
var auth = require('../controllers/auth');
var i18n = require('../i18n');
// -------- App --------
router.get('/', i18n.getUserLanguage, middleware.locals, function(req, res) {
router.get('/', i18n.getUserLanguage, locals, function(req, res) {
if (!req.headers['x-api-user'] && !req.headers['x-api-key'] && !(req.session && req.session.userId))
return res.redirect('/static/front')
@@ -23,7 +23,7 @@ router.get('/', i18n.getUserLanguage, middleware.locals, function(req, res) {
var pages = ['front', 'privacy', 'terms', 'api', 'features', 'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines', 'old-news', 'press-kit'];
_.each(pages, function(name){
router.get('/static/' + name, i18n.getUserLanguage, middleware.locals, function(req, res) {
router.get('/static/' + name, i18n.getUserLanguage, locals, function(req, res) {
res.render('static/' + name, {env: res.locals.habitrpg});
});
})
+8 -8
View File
@@ -30,8 +30,6 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) {
// Setup translations
var i18n = require('./i18n');
var middleware = require('./middleware');
var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14;
var app = express();
var server = http.createServer();
@@ -89,16 +87,18 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) {
var publicDir = path.join(__dirname, "/../public");
app.set("port", nconf.get('PORT'));
middleware.apiThrottle(app);
app.use(middleware.domainMiddleware(server,mongoose));
require('./middlewares/apiThrottle')(app);
app.use(require('./middlewares/domain')(server,mongoose));
if (!isProd) app.use(express.logger("dev"));
app.use(express.compress());
app.set("views", __dirname + "/../views");
app.set("view engine", "jade");
app.use(express.favicon(publicDir + '/favicon.ico'));
app.use(middleware.cors);
app.use(middleware.forceHabitica);
app.use(middleware.forceSSL);
app.use(require('./middlewares/cors'));
var redirects = require('./middlewares/redirects');
app.use(redirects.forceHabitica);
app.use(redirects.forceSSL);
app.use(express.urlencoded());
app.use(express.json());
app.use(require('method-override')());
@@ -134,7 +134,7 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) {
app.use('/api/v1', require('./routes/apiv1').middleware);
app.use('/export', require('./routes/dataexport').middleware);
require('./routes/apiv2.coffee')(swagger, v2);
app.use(middleware.errorHandler);
app.use(require('./middlewares/errorHandler'));
server.on('request', app);
server.listen(app.get("port"), function() {
+8 -1
View File
@@ -8,8 +8,15 @@ html(ng-app="habitrpg")
meta(name='viewport', content='width=device-width, initial-scale=1.0')
meta(name='apple-mobile-web-app-capable', content='yes')
// .slice(0).push('user') is to clone the array,
// to be surethat `user` is never avalaible to other requests' env
// TODO does it need only `user` in clientVars, not the others?
-
clientVars = env.clientVars.slice(0);
clientVars.push('user');
script(type='text/javascript').
window.env = !{JSON.stringify(env)};
window.env = !{JSON.stringify(env._.pick(env, clientVars))};
!= env.getManifestFiles("app")
File diff suppressed because one or more lines are too long
+1
View File
@@ -144,6 +144,7 @@
include ./chat-box
.alert.alert-info.alert-sm
!= ' ' + env.t('tavernAlert1') + ' <a href="https://github.com/HabitRPG/habitrpg/issues/2760" target="_blank">' + env.t('tavernAlert2') + '</a>.<br />' + env.t('moderatorIntro1')
//- FIXME this could be rendered server-side?
span(ng-repeat='mod in env.mods')
|&nbsp; &nbsp;
span(ng-if='::mod.contributor.admin',popover=env.t('gamemaster'),popover-trigger='mouseenter',popover-placement='right')
+1 -1
View File
@@ -6,7 +6,7 @@ script(id='modals/cards.html', type='text/ng-template')
.bg-info(style='padding:10px')
p {{::env.t('toAndFromCard', { toName: user.profile.name, fromName: user.items.special[cardType + 'Received'][0]})}}
hr
markdown(text='{{::cardMessage}}')
markdown(text='::cardMessage')
.modal-footer
small.pull-left {{::env.t(cardType + 'CardExplanation')}}
button.btn.btn-default(ng-click='user.ops.readCard({params: {cardType: cardType}}); $close()')=env.t('ok')
@@ -1,18 +1,23 @@
ul.items.rewards(ng-if='main && list.type=="reward"')
li.task.reward-item(ng-repeat='item in itemStore',popover-trigger='mouseenter', popover-placement='top', popover='{{item.key == "armoire" && !user.flags.armoireEmpty ? env.t("armoireNotesFull") + armoireCount(user.items.gear.owned) : item.notes()}}')
mixin reward(item)
li.task.reward-item(popover-trigger='mouseenter', popover-placement='top', popover='{{::#{item}.notes()}}')&attributes(attributes)
// right-hand side control buttons
.task-meta-controls
span.task-notes
span.glyphicon.glyphicon-comment
//left-hand size commands
.task-controls.task-primary
input.visuallyhidden(
class="reward",
input.reward.visuallyhidden(
type='checkbox',
ui-keypress='{13:"buy(item)"}')
a.money.btn-buy.item-btn(ng-class='{highValue: item.value >= 1000}', ng-click='buy(item)')
ui-keypress='{13:"buy(#{item})"}')
a.money.btn-buy.item-btn(ng-class='::{highValue: #{item}.value >= 1000}', ng-click='::buy(#{item})')
span.shop_gold
span.reward-cost {{item.value}}
span.reward-cost {{::#{item}.value}}
// main content
span(ng-class='::{"shop_{{item.key}} shop-sprite item-img": true}').reward-img
p.task-text {{item.text()}}
span(ng-class='::{"shop_{{#{item}.key}} shop-sprite item-img": true}').reward-img
p.task-text {{::#{item}.text()}}
ul.items.rewards(ng-if='main && list.type=="reward"')
+reward('item')(ng-repeat='item in itemStore')
+reward('healthPotion')
+reward('armoire')(ng-if='user.flags.armoireEnabled',
popover='{{armoire.notes(user, armoireCount(user.items.gear.owned))}}')
+54 -54
View File
@@ -26,7 +26,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
link(rel='stylesheet', type='text/css', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.min.css')
script(type='text/javascript').
window.env = !{JSON.stringify(env)};
window.env = !{JSON.stringify(env._.pick(env, env.clientVars))};
!= env.getManifestFiles("tmp_static_front")
script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js')
@@ -51,7 +51,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
span.icon-bar
span.icon-bar
a.navbar-brand(href='#')
img(src='https://s3.amazonaws.com/habitica-assets/assets/habitica_lockup2_desat.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/assets/habitica_lockup2_desat.png')
// Collect the nav links, forms, and other content for toggling
#bs-example-navbar-collapse-1.collapse.navbar-collapse
ul.nav.navbar-nav.navbar-right
@@ -69,7 +69,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
button#header-play-button.btn.btn-primary.navbar-btn.navbar-right(ng-click='playButtonClick()')= env.t('playButtonFull')
#intro
h1=env.t('motivate1')
img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/intro.png')
img.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/intro.png')
// insert intro images
.introcall.bg-success
h4= env.t('joinOthers')
@@ -78,13 +78,13 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.presslogos.text-center
= env.t('featuredIn')
br
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/lifehacker.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/nyt-logo.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/makeuseof.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/Forbes_logo.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/Cnetlogo.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/Fast-Company-logo.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/kickstarter-logo.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/lifehacker.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/nyt-logo.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/makeuseof.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/Forbes_logo.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/Cnetlogo.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/Fast-Company-logo.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/kickstarter-logo.png')
#intro-text
.container-fluid
.row
@@ -96,20 +96,20 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.row
.col-md-3.col-md-offset-1
h2= env.t('testimonialHeading')
img.img-say.img-responsive.visible-lg-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/publicSpaces.png')
img.img-say.img-responsive.visible-lg-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/publicSpaces.png')
// Testimonials
.col-md-8.col-sm-10.col-sx-10
#quote-carousel.carousel.slide(data-interval='10000', data-ride='carousel')
// Bottom Carousel Indicators
ol.carousel-indicators
li.active(data-target='#quote-carousel', data-slide-to='0')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Drag0nsilver.png', alt='')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Drag0nsilver.png', alt='')
li(data-target='#quote-carousel', data-slide-to='1')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/frabjabulous.png', alt='')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/frabjabulous.png', alt='')
li(data-target='#quote-carousel', data-slide-to='2')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/AndeeLiao.png', alt='')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/AndeeLiao.png', alt='')
li(data-target='#quote-carousel', data-slide-to='3')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/AlexandraSo.png', alt='')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/AlexandraSo.png', alt='')
// Carousel Slides / Quotes
.carousel-inner.text-center
// Quote 1
@@ -201,16 +201,16 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(220, 93, 93); border-right: 1px solid rgba(0, 0, 0, 0.25);') +
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; background-color: rgb(220, 93, 93); border-right: 0px none;') -
.task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('businessSample5')
img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/coding.png')
img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/coding.png')
.usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='frabjabulous', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/frabjabulous.png')
img(data-toggle='tooltip', data-placement='top', title='frabjabulous', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/frabjabulous.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
= env.t('frabjabulousQuote')
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='_AlexandraSo_', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/AlexandraSo.png')
img(data-toggle='tooltip', data-placement='top', title='_AlexandraSo_', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/AlexandraSo.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
@@ -245,16 +245,16 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(255, 207, 66); border-right: 1px solid rgba(0, 0, 0, 0.25);') +
| &nbsp;
.task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('healthSample5')
img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/clipart-rosemonkeyct-meditation.png')
img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/clipart-rosemonkeyct-meditation.png')
.usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='EvaGantz', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/EvaGantz.png')
img(data-toggle='tooltip', data-placement='top', title='EvaGantz', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/EvaGantz.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
= env.t('evagantzQuote')
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='supermouse35', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/supermouse35.png')
img(data-toggle='tooltip', data-placement='top', title='supermouse35', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/supermouse35.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
@@ -283,10 +283,10 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
| &nbsp;
.task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('teamSample3')
br
img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/publicSpaces.png')
img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/publicSpaces.png')
.usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='Althaire', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Althaire.png')
img(data-toggle='tooltip', data-placement='top', title='Althaire', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Althaire.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
@@ -321,15 +321,15 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(90, 98, 223); border-right: 1px solid rgba(0, 0, 0, 0.25);') +
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; background-color: rgb(90, 98, 223); border-right: 0px none;') -
.task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('schoolSample5')
img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/reading.png')
img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/reading.png')
.usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='InfH', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/InfH.png')
img(data-toggle='tooltip', data-placement='top', title='InfH', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/InfH.png')
.usetweet.tweet.popover.right
.arrow
.popover-content= env.t('infhQuote')
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='Drei-M', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Drei-M.png')
img(data-toggle='tooltip', data-placement='top', title='Drei-M', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Drei-M.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
@@ -364,10 +364,10 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(90, 98, 223); border-right: 1px solid rgba(0, 0, 0, 0.25);') +
| &nbsp;
.task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('goalSample5')
img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png')
img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png')
.usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='Kazui', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Kazui.png')
img(data-toggle='tooltip', data-placement='top', title='Kazui', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Kazui.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
@@ -402,15 +402,15 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(90, 98, 223); border-right: 1px solid rgba(0, 0, 0, 0.25);') +
| &nbsp;
.task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('choreSample5')
img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/dusting_by_leephon.png')
img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/dusting_by_leephon.png')
.usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='autumnesquirrel', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/autumnesquirrel.png')
img(data-toggle='tooltip', data-placement='top', title='autumnesquirrel', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/autumnesquirrel.png')
.usetweet.tweet.popover.right
.arrow
.popover-content= env.t('autumnesquirrelQuote')
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='irishfeet123', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/irishfeet123.png')
img(data-toggle='tooltip', data-placement='top', title='irishfeet123', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/irishfeet123.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
@@ -430,7 +430,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
br
= env.t('rewardByline2')
.scrolltweet.hidden-xs.hidden-sm
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='Elmi', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Elmi.png')
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='Elmi', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Elmi.png')
.tweet.popover.right.pull-right
.arrow
.popover-content= env.t('elmiQuote')
@@ -440,12 +440,12 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
span.glyphicon.glyphicon-check
h2
span.glyphicon.glyphicon-arrow-down
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/misc/shop_gold.png')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/misc/shop_gold.png')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/misc/shop_gold.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/misc/shop_gold.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/misc/shop_gold.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/misc/shop_gold.png')
h2
span.glyphicon.glyphicon-arrow-down
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/TVreward.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/TVreward.png')
button.btn.btn-primary.btn-lg.fixedcta.gamifybutton(ng-click='playButtonClick()')= env.t('gamifyButton')
section#levels.container-fluid
.row
@@ -459,17 +459,17 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
br
= env.t('unlockByline2')
.scrolltweet.hidden-xs.hidden-sm
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='16bitFil', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/16bitFil.png')
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='16bitFil', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/16bitFil.png')
.tweet.popover.right.pull-right
.arrow
.popover-content
= env.t('sixteenBitFilQuote')
.col-lg-4.col-md-4.col-lg-offset-1.col-sm-8.well.col-xs-8.col-xs-offset-2.col-sm-offset-2
.avatarscroll
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/avatar/avatarstatic.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/avatar/avatarstatic.png')
h2
span.glyphicon.glyphicon-arrow-down
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/SAMPLEadventurers.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/SAMPLEadventurers.png')
button.btn.btn-primary.btn-lg.fixedcta.gamifybutton(ng-click='playButtonClick()')= env.t('gamifyButton')
section#health.bg-danger.container-fluid
.row
@@ -480,7 +480,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
h2= env.t('punishHeading2')
p.sectioninfo= env.t('punishByline')
.scrolltweet.hidden-xs.hidden-sm
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='Zelah Meyer', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Zelah_Meyer.png')
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='Zelah Meyer', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Zelah_Meyer.png')
.tweet.popover.right.pull-right
.arrow
.popover-content
@@ -515,13 +515,13 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
br
= env.t('questByline2')
.scrolltweet.hidden-xs.hidden-sm
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='skysailor', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/skysailor.png')
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='skysailor', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/skysailor.png')
.tweet.popover.right.pull-right
.arrow
.popover-content
= env.t('skysailorQuote')
.col-lg-4.col-md-6.col-md-offset-1.col-lg-offset-1.col-sm-10.well.col-xs-10.col-xs-offset-1.col-sm-offset-1.well-lg
img.clearfix.img-responsive.center-block(style='padding-bottom:30px;', src="https://s3.amazonaws.com/habitrpg-assets/front/images/Quest_dilatory_drag'onSmall.png")
img.clearfix.img-responsive.center-block(style='padding-bottom:30px;', src="https://d2afqr2xdmyzvu.cloudfront.net/front/images/Quest_dilatory_dragonSmall.png")
.row
.col-lg-5.col-md-5.col-sm-5.col-sm-offset-0.col-xs-5.col-xs-offset-0
.health-progress
@@ -531,7 +531,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.bar
div(style='width:20%;') &nbsp;
.quest-friend-img
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/party/AnnaCosplay.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/party/AnnaCosplay.png')
.col-md-1.col-sm-1.col-xs-1
h2
span.glyphicon.glyphicon-arrow-right
@@ -543,7 +543,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.bar
div(style='width:0%;') &nbsp;
.quest-friend-img
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/party/GrimReaper.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/party/GrimReaper.png')
button.btn.btn-primary.btn-lg.fixedcta.gamifybutton(ng-click='playButtonClick()')= env.t('gamifyButton')
section#features.bg-info.container-fluid
.row
@@ -551,29 +551,29 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.row
.col-md-4.col-md-offset-2.col-sm-6
.feature-img
img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/Mount.png')
img.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/Mount.png')
.featuretext
h4= env.t('featurePetHeading')
p
= env.t('featurePetByline')
.col-md-4.col-sm-6
.feature-img.center-block
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/achievement-triadbingo.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/achievement-perfect.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/achievement-triadbingo.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/achievement-perfect.png')
.featuretext
h4= env.t('featureAchievementHeading')
p= env.t('featureAchievementByline')
.row
.col-md-4.col-md-offset-2.col-sm-6
.feature-img
img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/Promo_springclasses2015.png')
img.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/Promo_springclasses2015.png')
.featuretext
h4= env.t('featureEquipHeading')
p
= env.t('featureEquipByline')
.col-md-4.col-sm-6
.feature-img
img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/Party-Header.png')
img.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/Party-Header.png')
.featuretext
h4= env.t('featureSocialHeading')
p
@@ -593,19 +593,19 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.row
.col-lg-2.col-md-2.col-md-offset-1.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1
a(href='http://ionicframework.com/')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/ionic-logo-horizontal-transparent.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/ionic-logo-horizontal-transparent.png')
.col-lg-2.col-lg-offset-0.col-md-2.col-md-offset-0.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1
a(href='https://www.jetbrains.com/webstorm/')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/logo_webstorm.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/logo_webstorm.png')
.col-lg-2.col-lg-offset-0.col-md-2.col-md-offset-0.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1
a(href='http://github.com/')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/GitHub_Logo.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/GitHub_Logo.png')
.col-lg-2.col-lg-offset-0.col-md-2.col-md-offset-0.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1
a(href='https://trello.com/')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/trello-logo-blue.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/trello-logo-blue.png')
.col-lg-2.col-lg-offset-0.col-md-2.col-md-offset-0.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1
a(href='https://slack.com/')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/landing_slack_hash_wordmark_logo.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/landing_slack_hash_wordmark_logo.png')
.row.footer-content
include ../shared/footer
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -7,7 +7,7 @@ html(ng-app='habitrpg')
title=env.t('newStuff')
script(type='text/javascript').
window.env = !{JSON.stringify(env)};
window.env = !{JSON.stringify(env._.pick(env, env.clientVars))};
link(rel='shortcut icon', href='#{env.getBuildUrl("favicon.ico")}?v=4')