mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-07-22 03:34:14 +00:00
refactor(bs3): sync with develop
This commit is contained in:
commit
56133894d8
18 changed files with 137 additions and 64 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -12,6 +12,8 @@ newrelic_agent.log
|
|||
.bower-tmp
|
||||
.bower-registry
|
||||
.bower-cache
|
||||
|
||||
*.log
|
||||
src/*/*.map
|
||||
src/*/*/*.map
|
||||
test/*.js
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ Gruntfile.js
|
|||
CHANGELOG.md
|
||||
.idea*
|
||||
.git*
|
||||
*.log
|
||||
newrelic_agent.log
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ module.exports = function(grunt) {
|
|||
|
||||
nodemon: {
|
||||
dev: {
|
||||
ignoredFiles: ['public/*', 'Gruntfile.js', 'CHANGELOG.md', 'views/*', 'build/*', '.idea*', '.git*']
|
||||
ignoredFiles: ['public/*', 'Gruntfile.js', 'CHANGELOG.md', 'views/*', 'build/*', '.idea*', '.git*', '*.log']
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
"SMTP_USER":"user@domain.com",
|
||||
"SMTP_PASS":"password",
|
||||
"SMTP_SERVICE":"Gmail",
|
||||
"SMTP_HOST":"smtp.gmail.com",
|
||||
"SMTP_PORT": 587,
|
||||
"SMTP_TLS": true,
|
||||
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
|
||||
"PAYPAL_MERCHANT":"paypal-merchant@gmail.com",
|
||||
|
|
|
|||
|
|
@ -45,7 +45,10 @@
|
|||
"passport": "~0.1.18",
|
||||
"passport-facebook": "~1.0.2",
|
||||
"newrelic": "~1.3.0",
|
||||
"connect-ratelimit": "0.0.6"
|
||||
"connect-ratelimit": "0.0.6",
|
||||
"winston": "~0.7.2",
|
||||
"winston-mail": "~0.2.7",
|
||||
"winston-newrelic": "~0.1.4"
|
||||
},
|
||||
"private": true,
|
||||
"subdomain": "habitrpg",
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
|||
}
|
||||
|
||||
$scope.questAbort = function(){
|
||||
if (!confirm("Are you sure you want to abort this mission? It will abort it for everyone in your party, and you'll lose your quest scroll.")) return;
|
||||
if (!confirm("Are you sure you want to abort this mission? It will abort it for everyone in your party, all progress will be lost.")) return;
|
||||
if (!confirm("Are you double sure? Make sure they won't hate you forever!")) return;
|
||||
$rootScope.party.$questAbort();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ var shared = require('habitrpg-shared');
|
|||
var User = require('./../models/user').model;
|
||||
var Group = require('./../models/group').model;
|
||||
var Challenge = require('./../models/challenge').model;
|
||||
var logging = require('./../logging');
|
||||
var csv = require('express-csv');
|
||||
var api = module.exports;
|
||||
|
||||
|
|
@ -214,7 +215,7 @@ api.update = function(req, res){
|
|||
// Compare whether any changes have been made to tasks. If so, we'll want to sync those changes to subscribers
|
||||
if (before.isOutdated(req.body)) {
|
||||
User.find({_id: {$in: saved.members}}, function(err, users){
|
||||
console.log('Challenge updated, sync to subscribers');
|
||||
logging.info('Challenge updated, sync to subscribers');
|
||||
if (err) throw err;
|
||||
_.each(users, function(user){
|
||||
saved.syncToUser(user);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ var sanitize = validator.sanitize;
|
|||
var User = require('./../models/user').model;
|
||||
var Group = require('./../models/group').model;
|
||||
var Challenge = require('./../models/challenge').model;
|
||||
var logging = require('./../logging');
|
||||
var acceptablePUTPaths;
|
||||
var api = module.exports;
|
||||
|
||||
|
|
@ -349,7 +350,7 @@ api.buyGemsPaypalIPN = function(req, res, next) {
|
|||
user.balance += 5;
|
||||
//user.purchased.ads = true;
|
||||
user.save();
|
||||
console.log('PayPal transaction completed and user updated');
|
||||
logging.info('PayPal transaction completed and user updated');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -518,4 +519,4 @@ api.batchUpdate = function(req, res, next) {
|
|||
res.json(200, {_v: response._v});
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
|||
49
src/logging.js
Normal file
49
src/logging.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
var nconf = require('nconf');
|
||||
var winston = require('winston');
|
||||
require('winston-mail').Mail;
|
||||
require('winston-newrelic');
|
||||
|
||||
var logger;
|
||||
|
||||
if (logger == null) {
|
||||
logger = new (winston.Logger)({});
|
||||
if (nconf.get('NODE_ENV') == 'production') {
|
||||
logger.add(winston.transports.newrelic, {});
|
||||
logger.add(winston.transports.Mail, {
|
||||
to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'),
|
||||
from: "HabitRPG <" + nconf.get('SMTP_USER') + ">",
|
||||
subject: "HabitRPG Error",
|
||||
host: nconf.get('SMTP_HOST'),
|
||||
port: nconf.get('SMTP_PORT'),
|
||||
tls: nconf.get('SMTP_TLS'),
|
||||
username: nconf.get('SMTP_USER'),
|
||||
password: nconf.get('SMTP_PASS'),
|
||||
level: 'error'
|
||||
});
|
||||
} else {
|
||||
logger.add(winston.transports.Console, {colorize:true});
|
||||
logger.add(winston.transports.File, {filename: 'habitrpg.log'});
|
||||
}
|
||||
}
|
||||
|
||||
// A custom log function that wraps Winston. Makes it easy to instrument code
|
||||
// and still possible to replace Winston in the future.
|
||||
module.exports.log = function(/* variable args */) {
|
||||
if (logger)
|
||||
logger.log.apply(logger, arguments);
|
||||
};
|
||||
|
||||
module.exports.info = function(/* variable args */) {
|
||||
if (logger)
|
||||
logger.info.apply(logger, arguments);
|
||||
};
|
||||
|
||||
module.exports.warn = function(/* variable args */) {
|
||||
if (logger)
|
||||
logger.warn.apply(logger, arguments);
|
||||
};
|
||||
|
||||
module.exports.error = function(/* variable args */) {
|
||||
if (logger)
|
||||
logger.error.apply(logger, arguments);
|
||||
};
|
||||
|
|
@ -4,6 +4,7 @@ var fs = require('fs');
|
|||
var path = require('path');
|
||||
var User = require('./models/user').model
|
||||
var limiter = require('connect-ratelimit');
|
||||
var logging = require('./logging');
|
||||
|
||||
module.exports.apiThrottle = function(app) {
|
||||
app.use(limiter({
|
||||
|
|
@ -16,7 +17,7 @@ module.exports.apiThrottle = function(app) {
|
|||
}
|
||||
}
|
||||
})).use(function(req,res,next){
|
||||
//console.log(res.ratelimit);
|
||||
//logging.info(res.ratelimit);
|
||||
if (res.ratelimit.exceeded) return res.json(429,{err:'Rate limit exceeded'});
|
||||
next();
|
||||
});
|
||||
|
|
@ -92,15 +93,15 @@ var getManifestFiles = function(page){
|
|||
var css = '';
|
||||
|
||||
_.each(files.css, function(file){
|
||||
css += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
|
||||
css += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
|
||||
});
|
||||
|
||||
if(nconf.get('NODE_ENV') === 'production'){
|
||||
return css + '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
|
||||
return css + '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
|
||||
}else{
|
||||
var results = css;
|
||||
_.each(files.js, function(file){
|
||||
results += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
|
||||
results += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
|
@ -184,16 +185,16 @@ var getUserLanguage = function(req, callback){
|
|||
}
|
||||
});
|
||||
}else{
|
||||
return callback(null, _.find(avalaibleLanguages, {code: getFromBrowser()}));
|
||||
return callback(null, _.find(avalaibleLanguages, {code: getFromBrowser()}));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.locals = function(req, res, next) {
|
||||
getUserLanguage(req, function(err, language){
|
||||
if(err) return res.json(500, {err: err});
|
||||
if(err) return res.json(500, {err: err});
|
||||
|
||||
language.momentLang = (momentLangs[language.code] || undefined);
|
||||
|
||||
|
||||
res.locals.habitrpg = {
|
||||
NODE_ENV: nconf.get('NODE_ENV'),
|
||||
BASE_URL: nconf.get('BASE_URL'),
|
||||
|
|
@ -209,11 +210,11 @@ module.exports.locals = function(req, res, next) {
|
|||
var string = translations[language.code][stringName];
|
||||
if(!string) return _.template(translations[language.code].stringNotFound, {string: stringName});
|
||||
|
||||
return vars === undefined ? string : _.template(string, vars);
|
||||
return vars === undefined ? string : _.template(string, vars);
|
||||
},
|
||||
siteVersion: siteVersion
|
||||
}
|
||||
|
||||
next();
|
||||
next();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ var icalendar = require('icalendar');
|
|||
var api = require('./../controllers/user');
|
||||
var auth = require('./../controllers/auth');
|
||||
var middleware = require('../middleware');
|
||||
var logging = require('./../logging');
|
||||
|
||||
/* ---------- Deprecated API ------------*/
|
||||
|
||||
|
|
@ -78,7 +79,7 @@ var batchUpdate = function(req, res, next) {
|
|||
req.body = action.data;
|
||||
res.send = res.json = function(code, data) {
|
||||
if (_.isNumber(code) && code >= 400) {
|
||||
console.error({
|
||||
logging.error({
|
||||
code: code,
|
||||
data: data
|
||||
});
|
||||
|
|
@ -168,4 +169,4 @@ router.get('*', deprecated);
|
|||
router.post('*', deprecated);
|
||||
router.put('*', deprecated);
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
require('coffee-script') // for habitrpg-shared
|
||||
var nconf = require('nconf');
|
||||
var utils = require('./utils');
|
||||
var logging = require('./logging');
|
||||
utils.setupConfig();
|
||||
var async = require('async');
|
||||
var mongoose = require('mongoose');
|
||||
|
|
@ -15,7 +16,7 @@ async.waterfall([
|
|||
Group.findById('habitrpg', cb);
|
||||
},
|
||||
function(tavern, cb){
|
||||
console.log({tavern:tavern,cb:cb});
|
||||
logging.info({tavern:tavern,cb:cb});
|
||||
if (!tavern) {
|
||||
tavern = new Group({
|
||||
_id: 'habitrpg',
|
||||
|
|
@ -31,6 +32,6 @@ async.waterfall([
|
|||
}
|
||||
],function(err){
|
||||
if (err) throw err;
|
||||
console.log("Done initializing database");
|
||||
logging.info("Done initializing database");
|
||||
mongoose.disconnect();
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ var _ = require('lodash');
|
|||
var nconf = require('nconf');
|
||||
var utils = require('./utils');
|
||||
utils.setupConfig();
|
||||
|
||||
var logging = require('./logging');
|
||||
var isProd = nconf.get('NODE_ENV') === 'production';
|
||||
var isDev = nconf.get('NODE_ENV') === 'development';
|
||||
|
||||
|
|
@ -12,11 +12,11 @@ if (cluster.isMaster && (isDev || isProd)) {
|
|||
// Fork workers.
|
||||
_.times(require('os').cpus().length, function(){
|
||||
cluster.fork();
|
||||
})
|
||||
});
|
||||
|
||||
cluster.on('exit', function(worker, code, signal) {
|
||||
cluster.on('disconnect', function(worker, code, signal) {
|
||||
var w = cluster.fork(); // replace the dead worker
|
||||
console.error('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid);
|
||||
logging.info('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid);
|
||||
});
|
||||
|
||||
} else {
|
||||
|
|
@ -40,7 +40,7 @@ if (cluster.isMaster && (isDev || isProd)) {
|
|||
require('./models/challenge');
|
||||
mongoose.connect(nconf.get('NODE_DB_URI'), {auto_reconnect:true}, function(err) {
|
||||
if (err) throw err;
|
||||
console.info('Connected with Mongoose');
|
||||
logging.info('Connected with Mongoose');
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -135,14 +135,15 @@ if (cluster.isMaster && (isDev || isProd)) {
|
|||
app.use('/api/v1', require('./routes/apiv1').middleware);
|
||||
app.use('/export', require('./routes/dataexport').middleware);
|
||||
|
||||
app.use(utils.crashWorker(server));
|
||||
app.use(utils.errorHandler);
|
||||
|
||||
require('./routes/apiv2.coffee')(swagger, v2);
|
||||
|
||||
server.on('request', app);
|
||||
server.listen(app.get("port"), function() {
|
||||
return console.log("Express server listening on port " + app.get("port"));
|
||||
return logging.info("Express server listening on port " + app.get("port"));
|
||||
});
|
||||
|
||||
module.exports = server;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
src/utils.js
44
src/utils.js
|
|
@ -2,18 +2,20 @@ var nodemailer = require('nodemailer');
|
|||
var nconf = require('nconf');
|
||||
var crypto = require('crypto');
|
||||
var path = require("path");
|
||||
var cluster = require("cluster");
|
||||
|
||||
module.exports.sendEmail = function(mailData) {
|
||||
var smtpTransport = nodemailer.createTransport("SMTP",{
|
||||
service: nconf.get('SMTP_SERVICE'),
|
||||
service: nconf.get('SMTP_SERVICE'),
|
||||
auth: {
|
||||
user: nconf.get('SMTP_USER'),
|
||||
pass: nconf.get('SMTP_PASS')
|
||||
user: nconf.get('SMTP_USER'),
|
||||
pass: nconf.get('SMTP_PASS')
|
||||
}
|
||||
});
|
||||
smtpTransport.sendMail(mailData, function(error, response){
|
||||
if(error) console.log(error);
|
||||
else console.log("Message sent: " + response.message);
|
||||
var logging = require('./logging');
|
||||
if(error) logging.error(error);
|
||||
else logging.info("Message sent: " + response.message);
|
||||
smtpTransport.close(); // shut down the connection pool, no more messages
|
||||
});
|
||||
}
|
||||
|
|
@ -48,7 +50,8 @@ module.exports.setupConfig = function(){
|
|||
// // * https://developers.google.com/chrome-developer-tools/docs/heap-profiling
|
||||
// // * https://developers.google.com/chrome-developer-tools/docs/memory-analysis-101
|
||||
// agent = require('webkit-devtools-agent');
|
||||
// console.log("To debug memory leaks:" +
|
||||
// var logging = require('./logging');
|
||||
// logging.info("To debug memory leaks:" +
|
||||
// "\n\t(1) Run `kill -SIGUSR2 " + process.pid + "`" +
|
||||
// "\n\t(2) open http://c4milo.github.com/node-webkit-agent/21.0.1180.57/inspector.html?host=localhost:1337&page=0");
|
||||
// }
|
||||
|
|
@ -59,6 +62,23 @@ module.exports.setupConfig = function(){
|
|||
if (nconf.get('NODE_ENV') === 'production') require('newrelic');
|
||||
};
|
||||
|
||||
module.exports.crashWorker = function(server) {
|
||||
return function(err, req, res, next) {
|
||||
if (!cluster.isMaster) {
|
||||
// make sure we close down within 30 seconds
|
||||
var killtimer = setTimeout(function() {
|
||||
process.exit(1);
|
||||
}, 30000);
|
||||
// But don't keep the process open just for that!
|
||||
killtimer.unref();
|
||||
// stop taking new requests.
|
||||
server.close();
|
||||
cluster.worker.disconnect();
|
||||
}
|
||||
next(err);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
module.exports.errorHandler = function(err, req, res, next) {
|
||||
// when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER)
|
||||
|
|
@ -69,15 +89,9 @@ module.exports.errorHandler = function(err, req, res, next) {
|
|||
"\n\nheaders: " + JSON.stringify(req.headers) +
|
||||
"\n\nbody: " + JSON.stringify(req.body) +
|
||||
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
|
||||
module.exports.sendEmail({
|
||||
from: "HabitRPG <" + nconf.get('SMTP_USER') + ">",
|
||||
to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'),
|
||||
subject: "HabitRPG Error",
|
||||
text: stack
|
||||
});
|
||||
console.error(stack);
|
||||
var logging = require('./logging');
|
||||
logging.error(stack);
|
||||
var message = err.message ? err.message : err;
|
||||
message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length);
|
||||
res.json(500,{err:message}); //res.end(err.message);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ script(type='text/ng-template', id='partials/options.inventory.pets.html')
|
|||
menu
|
||||
div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.text}}', popover-placement='bottom', ng-init='pet = egg.key+"-"+potion.key')
|
||||
button(class="pet-button Pet-{{pet}}", ng-if='user.items.pets[pet]>0', ng-class='{active: user.items.currentPet == pet, selectableInventory: selectedFood}', ng-click='choosePet(egg.key, potion.key)')
|
||||
.progress(ng-class='{"progress-success": user.items.pets[pet]<50}')
|
||||
.progress(ng-show='!user.items.mounts[pet]', ng-class='{"progress-success": user.items.pets[pet]<50}')
|
||||
.bar(style="width: {{user.items.pets[pet]/.5}}%;")
|
||||
button(class="pet-button pet-not-owned", ng-if='!user.items.pets[pet]')
|
||||
.PixelPaw
|
||||
|
|
|
|||
|
|
@ -30,22 +30,15 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
|
|||
| 2 <span class="Pet_Currency_Gem1x inline-gems"/> /
|
||||
= ' ' + env.t('locked')
|
||||
menu(type='list')
|
||||
// For special events code, see commit dfa27b3
|
||||
|
||||
// Color
|
||||
li.customize-menu
|
||||
menu(label=env.t('color'))
|
||||
each v,k in {'c8c8c8':'white','903a00':'brown','cfb853':'blond','ec720f':'red','2e2e2e':'black'}
|
||||
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:##{k};', ng-click='set({"preferences.hair.color": "#{v}"})')
|
||||
|
||||
// Special Events
|
||||
li.customize-menu.well.limited-edition
|
||||
.label.label-info.pull-right(popover=env.t('limited31Jan'), popover-title=env.t('limitedEdition'), popover-placement='right', popover-trigger='mouseenter')
|
||||
=env.t('limitedEdition')
|
||||
|
|
||||
span.glyphicon.glyphicon-question-sign
|
||||
menu(label=env.t('winterColors'))
|
||||
each color in ['candycane','frost','winternight','holly']
|
||||
button(type='button', ng-class='{locked: !user.purchased.hair.color.#{color}}', class='customize-option hair_bangs_1_#{color}', style='width: 40px; height: 40px;', ng-click='unlock("hair.color.#{color}")')
|
||||
button.btn.btn-small.btn-primary(ng-hide='user.purchased.hair.color.candycane && user.purchased.hair.color.frost && user.purchased.hair.color.winternight && user.purchased.hair.color.holly', ng-click='unlock("hair.color.candycane,hair.color.frost,hair.color.winternight,hair.color.holly")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
||||
button(type='button', ng-if='user.purchased.hair.color.#{color}', class='customize-option hair_bangs_1_#{color}', style='width: 40px; height: 40px;', ng-click='unlock("hair.color.#{color}")')
|
||||
|
||||
h5=env.t('bodyHair')
|
||||
// Bangs
|
||||
|
|
@ -160,22 +153,23 @@ script(id='partials/options.profile.stats.html', type='text/ng-template')
|
|||
span.glyphicon.glyphicon-download
|
||||
|
||||
=env.t('distributePoints')
|
||||
tr
|
||||
td= env.t('allocateStr') + ' {{user.stats.str}}'
|
||||
td
|
||||
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("str")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateStrPop')) +
|
||||
tr
|
||||
td= env.t('allocateCon') + ' {{user.stats.con}}'
|
||||
td
|
||||
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("con")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateConPop')) +
|
||||
tr
|
||||
td= env.t('allocateInt') + ' {{user.stats.int}}'
|
||||
td
|
||||
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("int")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateIntPop')) +
|
||||
tr
|
||||
td= env.t('allocatePer') + ' {{user.stats.per}}'
|
||||
td
|
||||
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("per")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocatePerPop')) +
|
||||
tr
|
||||
td= env.t('allocateInt') + ' {{user.stats.int}}'
|
||||
td= env.t('allocateStr') + ' {{user.stats.str}}'
|
||||
td
|
||||
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("int")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateIntPop')) +
|
||||
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("str")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateStrPop')) +
|
||||
|
||||
div(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "span4" : "span6"')
|
||||
include ../shared/profiles/achievements
|
||||
|
||||
|
|
|
|||
|
|
@ -63,15 +63,16 @@ table.table.table-striped
|
|||
|: {{profile.stats.buffs[k] || 0}}
|
||||
tr(ng-if='profile.stats.buffs.stealth')
|
||||
td
|
||||
i.icon-question-sign(popover-title=env.t('stealth'), popover-trigger='mouseenter', popover-placement='right', popover=env.t('stealthNewDay'))
|
||||
|
||||
strong=env.t('stealth')
|
||||
|: {{profile.stats.buffs.stealth}}
|
||||
span.glyphicon.glyphicon-question-sign(popover-title=env.t('stealth'), popover-trigger='mouseenter', popover-placement='right', popover=env.t('stealthNewDay'))
|
||||
|: {{profile.stats.buffs.stealth}}
|
||||
td
|
||||
tr(ng-if='profile.stats.buffs.streaks')
|
||||
td
|
||||
i.icon-question-sign(popover-title=env.t('streaksFrozen'), popover-trigger='mouseenter', popover-placement='right', popover=env.t('streaksFrozenText'))
|
||||
|
||||
strong=env.t('streaksFrozen')
|
||||
|
|
||||
span.glyphicon.glyphicon-question-sign(popover-title=env.t('streaksFrozen'), popover-trigger='mouseenter', popover-placement='right', popover=env.t('streaksFrozenText'))
|
||||
td
|
||||
|
||||
h4(ng-show='user.flags.dropsEnabled')=env.t('pets')
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ li(bindonce='list', bo-id='"task-"+task.id', ng-repeat='task in obj[list.type+"s
|
|||
a.badge(ng-if='task.checklist[0]', ng-class='{"badge-success":checklistCompletion(task.checklist) == task.checklist.length}', ng-click='collapseChecklist(task)', style='margin:1px')
|
||||
{{checklistCompletion(task.checklist)}}/{{task.checklist.length}}
|
||||
|
|
||||
span.glyphicon.glyphicon-tags(tooltip='{{appliedTags(user.tags, task.tags)}}', ng-hide='Shared.noTags(task.tags)')
|
||||
span.glyphicon.glyphicon-tags(tooltip='{{Shared.appliedTags(user.tags, task.tags)}}', ng-hide='Shared.noTags(task.tags)')
|
||||
// edit
|
||||
a(ng-hide='task._editing', ng-click='editTask(task)', tooltip=env.t('edit'))
|
||||
|
|
||||
|
|
|
|||
Loading…
Reference in a new issue