Compare commits
148 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 289032047c | |||
| 6f5515214a | |||
| bbbc06733b | |||
| 92c3a640ee | |||
| bb8bd8842d | |||
| 1411706963 | |||
| e94631a002 | |||
| 39bb60638f | |||
| 71e34e654c | |||
| f01aba15be | |||
| fd4e760c05 | |||
| b5ed65b164 | |||
| 4ee14e7c2a | |||
| f40fb510a9 | |||
| 86951916e8 | |||
| fde8e54783 | |||
| 276e882092 | |||
| 88bfed7efe | |||
| 0936c2ff86 | |||
| bdedf8f563 | |||
| 9220323483 | |||
| 80c93ad934 | |||
| 010da977a4 | |||
| b9bfb3f722 | |||
| effb66a089 | |||
| dbdb5f81a5 | |||
| 2062e78959 | |||
| 018c5edfdd | |||
| b0a3e58d66 | |||
| 99960da2eb | |||
| 8c293505c4 | |||
| 10ac99fc2e | |||
| 34c7d4e3b8 | |||
| 50d9a355b0 | |||
| 35b5285ce6 | |||
| 2001b27c26 | |||
| 7064b363e0 | |||
| c5147a696d | |||
| bd8e67a2ea | |||
| db1bda1bcd | |||
| fe6c21800c | |||
| 2ff9dfe965 | |||
| 75068ceb9e | |||
| 462eac2599 | |||
| 3fe307f6ea | |||
| bb418da91f | |||
| 08856ecc9f | |||
| 9e925513b0 | |||
| d747e97cea | |||
| bccd8e0000 | |||
| 1035af0d25 | |||
| 80b302c997 | |||
| f448c8cdfb | |||
| 77195c64cf | |||
| 1efc030544 | |||
| 5ad30e815a | |||
| 966bcf8010 | |||
| 8ebda9f7bd | |||
| 6192b563e9 | |||
| 6763e178d4 | |||
| 6a9025200c | |||
| 4e3481445c | |||
| 8300464cfc | |||
| abb4899552 | |||
| 90f88c42f6 | |||
| a4f84342ca | |||
| a90d1187e0 | |||
| 88b130a219 | |||
| 637e179951 | |||
| 49e0077c67 | |||
| 0787582e58 | |||
| 1c942eb8f3 | |||
| d67f6ae471 | |||
| 55baed38b5 | |||
| 7ef55d0283 | |||
| 8fac74e812 | |||
| 3f2d1bf430 | |||
| 42cfc7d851 | |||
| ef7724203e | |||
| 09cec21cde | |||
| fc987c11e9 | |||
| 3678861c48 | |||
| a6463e1fba | |||
| 0e4d4c2235 | |||
| 09b76008ea | |||
| c2a79e1d7c | |||
| c5208f0ef6 | |||
| 979d0c519d | |||
| 3f5ee32684 | |||
| 6deee0ffc8 | |||
| a11e4d0512 | |||
| 118e3580d6 | |||
| 343f276705 | |||
| c00a1d74f9 | |||
| ae3f014bb2 | |||
| 15e6cef7c4 | |||
| b8c2d5eb20 | |||
| a444e876d1 | |||
| 24b347af8e | |||
| 76039020f5 | |||
| cbcfb21deb | |||
| f47ec3d85f | |||
| 517d56f9fc | |||
| a5c4871183 | |||
| aee08ba0f9 | |||
| e1f9cac37a | |||
| c9bb96d2be | |||
| b0d86ff37b | |||
| 3f3ebeffa1 | |||
| 1bb337d235 | |||
| 07d6969764 | |||
| 5807db9053 | |||
| 6543a43854 | |||
| a046930097 | |||
| 7579bec587 | |||
| c1e7fed4d4 | |||
| bad148148c | |||
| 0280513a00 | |||
| e502588abc | |||
| 5718d8396d | |||
| b59a63ecbe | |||
| f0465aab5e | |||
| 39ee78127c | |||
| 4658025f3f | |||
| 46417b4124 | |||
| 706a7b441a | |||
| a8baa8fcfe | |||
| 368cf91e32 | |||
| 23fc969432 | |||
| 6a5e1dda0e | |||
| 70326e5b5b | |||
| c91c152b53 | |||
| 362313fe0a | |||
| ed5dfd0228 | |||
| 0891908cba | |||
| b9a3ee7f30 | |||
| ea3938a91e | |||
| fdfbade493 | |||
| 15976e906a | |||
| ac52da5be2 | |||
| 1b39338eec | |||
| 6fe87a8140 | |||
| c5d0ff63a5 | |||
| 301b171f55 | |||
| 08baa2ca20 | |||
| 7c83cfe9b4 | |||
| 3045be3ddf | |||
| 81c58d2122 |
@@ -1,8 +1,6 @@
|
||||
Habitica  [](https://codeclimate.com/github/HabitRPG/habitrpg) [](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE) [](https://www.codetriage.com/habitrpg/habitica)
|
||||
===============
|
||||
|
||||
[](https://greenkeeper.io/)
|
||||
|
||||
[Habitica](https://habitica.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.
|
||||
|
||||
**We need more programmers!** Your assistance will be greatly appreciated. The wiki pages below and the additional pages they link to will tell you how to get started on contributing code and where you can go to seek further help or ask questions:
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"LOGGLY_SUBDOMAIN": "example-subdomain",
|
||||
"LOGGLY_TOKEN": "example-token",
|
||||
"MAINTENANCE_MODE": "false",
|
||||
"NODE_DB_URI": "mongodb://localhost/habitrpg",
|
||||
"NODE_DB_URI": "mongodb://localhost:27017/habitrpg",
|
||||
"MONGODB_POOL_SIZE": "10",
|
||||
"NODE_ENV": "development",
|
||||
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
||||
@@ -70,7 +70,7 @@
|
||||
"SLACK_URL": "https://hooks.slack.com/services/some-url",
|
||||
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
|
||||
"TEST_DB_URI": "mongodb://localhost/habitrpg_test",
|
||||
"TEST_DB_URI": "mongodb://localhost:27017/habitrpg_test",
|
||||
"TRANSIFEX_SLACK_CHANNEL": "transifex",
|
||||
"WEB_CONCURRENCY": 1,
|
||||
"SKIP_SSL_CHECK_KEY": "key",
|
||||
|
||||
@@ -8,13 +8,16 @@ const BASE_URL = nconf.get('BASE_URL');
|
||||
const EMAIL_SLUG = 'mandrill-email-slug'; // Set email template to send
|
||||
const MIGRATION_NAME = 'bulk-email';
|
||||
|
||||
const progressCount = 1000;
|
||||
const progressCount = 250;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count += 1;
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
if (count % progressCount === 0) {
|
||||
console.warn(`${count} ${user._id}`);
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
}
|
||||
|
||||
await sendTxn(
|
||||
user,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20190314_pi_day';
|
||||
const MIGRATION_NAME = '20200314_pi_day';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
@@ -24,27 +24,37 @@ async function updateUser (user) {
|
||||
'items.food.Pie_Red': 1,
|
||||
};
|
||||
const set = {};
|
||||
let push;
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
set['items.gear.owned.head_special_piDay'] = false;
|
||||
set['items.gear.owned.shield_special_piDay'] = false;
|
||||
const push = [
|
||||
{ type: 'marketGear', path: 'gear.flat.head_special_piDay', _id: uuid() },
|
||||
{ type: 'marketGear', path: 'gear.flat.shield_special_piDay', _id: uuid() },
|
||||
];
|
||||
if (typeof user.items.gear.owned.head_special_piDay !== 'undefined') {
|
||||
push = false;
|
||||
} else {
|
||||
set['items.gear.owned.head_special_piDay'] = false;
|
||||
set['items.gear.owned.shield_special_piDay'] = false;
|
||||
push = [
|
||||
{ type: 'marketGear', path: 'gear.flat.head_special_piDay', _id: uuid() },
|
||||
{ type: 'marketGear', path: 'gear.flat.shield_special_piDay', _id: uuid() },
|
||||
];
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
if (push) {
|
||||
return User
|
||||
.update({ _id: user._id }, { $inc: inc, $set: set, $push: { pinnedItems: { $each: push } } })
|
||||
.exec();
|
||||
}
|
||||
return User
|
||||
.update({ _id: user._id }, { $inc: inc, $set: set, $push: { pinnedItems: { $each: push } } })
|
||||
.update({ _id: user._id }, { $inc: inc, $set: set })
|
||||
.exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
const query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2019-02-15') },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2020-02-15') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.134.3",
|
||||
"version": "4.137.1",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.8.4",
|
||||
"@babel/preset-env": "^7.8.4",
|
||||
"@babel/register": "^7.8.3",
|
||||
"@babel/core": "^7.8.7",
|
||||
"@babel/preset-env": "^7.8.7",
|
||||
"@babel/register": "^7.8.6",
|
||||
"@google-cloud/trace-agent": "^4.2.5",
|
||||
"@slack/client": "^3.8.1",
|
||||
"accepts": "^1.3.5",
|
||||
@@ -14,7 +14,7 @@
|
||||
"amplitude": "^3.5.0",
|
||||
"apidoc": "^0.17.5",
|
||||
"apn": "^2.2.0",
|
||||
"aws-sdk": "^2.619.0",
|
||||
"aws-sdk": "^2.635.0",
|
||||
"bcrypt": "^3.0.8",
|
||||
"body-parser": "^1.18.3",
|
||||
"compression": "^1.7.4",
|
||||
@@ -34,10 +34,10 @@
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-imagemin": "^6.2.0",
|
||||
"gulp-nodemon": "^2.4.1",
|
||||
"gulp-nodemon": "^2.5.0",
|
||||
"gulp.spritesmith": "^6.9.0",
|
||||
"habitica-markdown": "^1.3.2",
|
||||
"helmet": "^3.21.2",
|
||||
"helmet": "^3.21.3",
|
||||
"image-size": "^0.8.3",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"js2xmlparser": "^4.0.1",
|
||||
@@ -46,7 +46,7 @@
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.24.0",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.8.11",
|
||||
"mongoose": "^5.9.3",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^1.0.2",
|
||||
@@ -63,14 +63,14 @@
|
||||
"rimraf": "^3.0.2",
|
||||
"short-uuid": "^3.0.0",
|
||||
"stripe": "^7.15.0",
|
||||
"superagent": "^5.2.1",
|
||||
"superagent": "^5.2.2",
|
||||
"universal-analytics": "^0.4.17",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^3.4.0",
|
||||
"validator": "^11.0.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"winston": "^2.4.3",
|
||||
"winston-loggly-bulk": "^2.0.2",
|
||||
"winston": "^3.2.1",
|
||||
"winston-loggly-bulk": "^3.0.1",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
"private": true,
|
||||
@@ -106,14 +106,14 @@
|
||||
"axios": "^0.19.2",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chalk": "^2.4.1",
|
||||
"chalk": "^3.0.0",
|
||||
"expect.js": "^0.3.1",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^7.1.2",
|
||||
"require-again": "^2.0.0",
|
||||
"sinon": "^7.2.4",
|
||||
"sinon-chai": "^3.4.0",
|
||||
"sinon-chai": "^3.5.0",
|
||||
"sinon-stub-promise": "^4.0.0"
|
||||
},
|
||||
"optionalDependencies": {}
|
||||
|
||||
@@ -34,11 +34,12 @@ async function deleteAmplitudeData (userId, email) {
|
||||
}
|
||||
|
||||
async function deleteHabiticaData (user, email) {
|
||||
const truncatedEmail = email.slice(0, email.indexOf('@'));
|
||||
await User.update(
|
||||
{ _id: user._id },
|
||||
{
|
||||
$set: {
|
||||
'auth.local.email': email,
|
||||
'auth.local.email': user.auth.local.email ? email : `${truncatedEmail}@example.com`,
|
||||
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
|
||||
'auth.local.passwordHashMethod': 'bcrypt',
|
||||
},
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
import winston from 'winston';
|
||||
import logger from '../../../../website/server/libs/logger';
|
||||
import logger, { _loggerConfig } from '../../../../website/server/libs/logger';
|
||||
import {
|
||||
NotFound,
|
||||
} from '../../../../website/server/libs/errors';
|
||||
|
||||
describe('logger', () => {
|
||||
let logSpy;
|
||||
let infoSpy;
|
||||
let warnSpy;
|
||||
let errorSpy;
|
||||
|
||||
const originalLoggingEnabled = _loggerConfig.loggingEnabled;
|
||||
|
||||
before(() => { // enable logging in tests
|
||||
_loggerConfig.loggingEnabled = true;
|
||||
});
|
||||
|
||||
after(() => { // reset value of _loggerConfig.loggingEnabled
|
||||
_loggerConfig.loggingEnabled = originalLoggingEnabled;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
logSpy = sandbox.stub(winston.Logger.prototype, 'log');
|
||||
infoSpy = sandbox.stub(_loggerConfig.logger, 'info');
|
||||
warnSpy = sandbox.stub(_loggerConfig.logger, 'warn');
|
||||
errorSpy = sandbox.stub(_loggerConfig.logger, 'error');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -18,8 +31,8 @@ describe('logger', () => {
|
||||
describe('info', () => {
|
||||
it('calls winston\'s info log', () => {
|
||||
logger.info(1, 2, 3);
|
||||
expect(logSpy).to.be.calledOnce;
|
||||
expect(logSpy).to.be.calledWith('info', 1, 2, 3);
|
||||
expect(infoSpy).to.be.calledOnce;
|
||||
expect(infoSpy).to.be.calledWith(1, 2, 3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,8 +40,8 @@ describe('logger', () => {
|
||||
context('non-error object', () => {
|
||||
it('passes through arguments if the first arg is not an error object', () => {
|
||||
logger.error(1, 2, 3, 4);
|
||||
expect(logSpy).to.be.calledOnce;
|
||||
expect(logSpy).to.be.calledWith('error', 1, 2, 3, 4);
|
||||
expect(errorSpy).to.be.calledOnce;
|
||||
expect(errorSpy).to.be.calledWith(1, 2, 3, 4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,9 +52,8 @@ describe('logger', () => {
|
||||
data: 1,
|
||||
}, 2, 3);
|
||||
|
||||
expect(logSpy).to.be.calledOnce;
|
||||
expect(logSpy).to.be.calledWith(
|
||||
'error',
|
||||
expect(errorSpy).to.be.calledOnce;
|
||||
expect(errorSpy).to.be.calledWith(
|
||||
errInstance.stack,
|
||||
{ data: 1, fullError: errInstance },
|
||||
2,
|
||||
@@ -58,9 +70,8 @@ describe('logger', () => {
|
||||
fullError: anotherError,
|
||||
}, 2, 3);
|
||||
|
||||
expect(logSpy).to.be.calledOnce;
|
||||
expect(logSpy).to.be.calledWith(
|
||||
'error',
|
||||
expect(errorSpy).to.be.calledOnce;
|
||||
expect(errorSpy).to.be.calledWith(
|
||||
errInstance.stack,
|
||||
{ data: 1, fullError: anotherError },
|
||||
2,
|
||||
@@ -73,9 +84,8 @@ describe('logger', () => {
|
||||
|
||||
logger.error(errInstance, null, 2, 3);
|
||||
|
||||
expect(logSpy).to.be.calledOnce;
|
||||
expect(logSpy).to.be.calledWith(
|
||||
'error',
|
||||
expect(errorSpy).to.be.calledOnce;
|
||||
expect(errorSpy).to.be.calledWith(
|
||||
errInstance.stack,
|
||||
null,
|
||||
2,
|
||||
@@ -88,9 +98,8 @@ describe('logger', () => {
|
||||
|
||||
logger.error(errInstance, true, 2, 3);
|
||||
|
||||
expect(logSpy).to.be.calledOnce;
|
||||
expect(logSpy).to.be.calledWith(
|
||||
'error',
|
||||
expect(errorSpy).to.be.calledOnce;
|
||||
expect(errorSpy).to.be.calledWith(
|
||||
errInstance.stack,
|
||||
true,
|
||||
2,
|
||||
@@ -103,9 +112,8 @@ describe('logger', () => {
|
||||
|
||||
logger.error(errInstance, { httpCode: 400 }, 2, 3);
|
||||
|
||||
expect(logSpy).to.be.calledOnce;
|
||||
expect(logSpy).to.be.calledWith(
|
||||
'error',
|
||||
expect(errorSpy).to.be.calledOnce;
|
||||
expect(errorSpy).to.be.calledWith(
|
||||
errInstance.stack,
|
||||
{ httpCode: 400, fullError: errInstance },
|
||||
2,
|
||||
@@ -121,9 +129,8 @@ describe('logger', () => {
|
||||
httpCode: 502,
|
||||
}, 2, 3);
|
||||
|
||||
expect(logSpy).to.be.calledOnce;
|
||||
expect(logSpy).to.be.calledWith(
|
||||
'error',
|
||||
expect(errorSpy).to.be.calledOnce;
|
||||
expect(errorSpy).to.be.calledWith(
|
||||
errInstance.stack,
|
||||
{ httpCode: 502, isHandledError: true, fullError: errInstance },
|
||||
2,
|
||||
@@ -139,9 +146,8 @@ describe('logger', () => {
|
||||
httpCode: 403,
|
||||
}, 2, 3);
|
||||
|
||||
expect(logSpy).to.be.calledOnce;
|
||||
expect(logSpy).to.be.calledWith(
|
||||
'warn',
|
||||
expect(warnSpy).to.be.calledOnce;
|
||||
expect(warnSpy).to.be.calledWith(
|
||||
errInstance.stack,
|
||||
{ httpCode: 403, isHandledError: true, fullError: errInstance },
|
||||
2,
|
||||
@@ -156,9 +162,8 @@ describe('logger', () => {
|
||||
|
||||
logger.error(errInstance, {}, 2, 3);
|
||||
|
||||
expect(logSpy).to.be.calledOnce;
|
||||
expect(logSpy).to.be.calledWith(
|
||||
'error',
|
||||
expect(errorSpy).to.be.calledOnce;
|
||||
expect(errorSpy).to.be.calledWith(
|
||||
errInstance.stack,
|
||||
{
|
||||
fullError: {
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('response middleware', () => {
|
||||
expect(res.json).to.be.calledWith({
|
||||
success: true,
|
||||
data: { field: 1 },
|
||||
notifications: [],
|
||||
notifications: res.locals.user.notifications,
|
||||
userV: res.locals.user._v,
|
||||
appVersion: packageInfo.version,
|
||||
});
|
||||
@@ -52,7 +52,7 @@ describe('response middleware', () => {
|
||||
success: true,
|
||||
data: { field: 1 },
|
||||
message: 'hello',
|
||||
notifications: [],
|
||||
notifications: res.locals.user.notifications,
|
||||
userV: res.locals.user._v,
|
||||
appVersion: packageInfo.version,
|
||||
});
|
||||
@@ -69,7 +69,7 @@ describe('response middleware', () => {
|
||||
expect(res.json).to.be.calledWith({
|
||||
success: false,
|
||||
data: { field: 1 },
|
||||
notifications: [],
|
||||
notifications: res.locals.user.notifications,
|
||||
userV: res.locals.user._v,
|
||||
appVersion: packageInfo.version,
|
||||
});
|
||||
@@ -84,42 +84,9 @@ describe('response middleware', () => {
|
||||
expect(res.json).to.be.calledWith({
|
||||
success: true,
|
||||
data: { field: 1 },
|
||||
notifications: [],
|
||||
notifications: res.locals.user.notifications,
|
||||
userV: 0,
|
||||
appVersion: packageInfo.version,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns notifications if a user is authenticated', () => {
|
||||
const { user } = res.locals;
|
||||
|
||||
user.notifications = [
|
||||
null, // invalid, not an object
|
||||
{ seen: true }, // invalid, no type or id
|
||||
{ id: 123 }, // invalid, no type
|
||||
// invalid, no id, not included here because the id would be added automatically
|
||||
// {type: 'ABC'},
|
||||
{ type: 'ABC', id: '123' }, // valid
|
||||
];
|
||||
|
||||
responseMiddleware(req, res, next);
|
||||
res.respond(200, { field: 1 });
|
||||
|
||||
expect(res.json).to.be.calledOnce;
|
||||
|
||||
expect(res.json).to.be.calledWith({
|
||||
success: true,
|
||||
data: { field: 1 },
|
||||
notifications: [
|
||||
{
|
||||
type: 'ABC',
|
||||
id: '123',
|
||||
data: {},
|
||||
seen: false,
|
||||
},
|
||||
],
|
||||
userV: res.locals.user._v,
|
||||
appVersion: packageInfo.version,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { sanitizeText } from '../../../../website/server/models/message';
|
||||
import { MAX_MESSAGE_LENGTH } from '../../../../website/common/script/constants';
|
||||
|
||||
describe('Message Model', () => {
|
||||
context('sanitizeText', () => {
|
||||
it('trims messages to the max length', () => {
|
||||
const veryLongMessage = `
|
||||
123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789.
|
||||
THIS PART WON'T BE IN THE MESSAGE (over 3000)
|
||||
`;
|
||||
expect(veryLongMessage.length > MAX_MESSAGE_LENGTH).to.equal(true);
|
||||
|
||||
const sanitizedText = sanitizeText(veryLongMessage);
|
||||
expect(sanitizedText).to.not.contain('MESSAGE');
|
||||
expect(sanitizedText.length).to.equal(MAX_MESSAGE_LENGTH);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { model as PushDevice } from '../../../../website/server/models/pushDevice';
|
||||
|
||||
describe('PushDevice Model', () => {
|
||||
context('cleanupCorruptData', () => {
|
||||
it('converts an array of push devices to a safe version', () => {
|
||||
const pushDevices = [
|
||||
null, // invalid, not an object
|
||||
{ regId: '123' }, // invalid, no type
|
||||
{ type: 'android' }, // invalid, no regId
|
||||
new PushDevice({ type: 'android', regId: '1234' }), // valid
|
||||
];
|
||||
|
||||
const safePushDevices = PushDevice.cleanupCorruptData(pushDevices);
|
||||
expect(safePushDevices.length).to.equal(1);
|
||||
expect(safePushDevices[0].type).to.equal('android');
|
||||
expect(safePushDevices[0].regId).to.equal('1234');
|
||||
});
|
||||
|
||||
it('removes duplicates', () => {
|
||||
const pushDevices = [
|
||||
new PushDevice({ type: 'android', regId: '1234' }),
|
||||
new PushDevice({ type: 'android', regId: '1234' }),
|
||||
new PushDevice({ type: 'iphone', regId: '1234' }), // not duplicate
|
||||
new PushDevice({ type: 'android', regId: '12345' }), // not duplicate
|
||||
];
|
||||
|
||||
const safePushDevices = PushDevice.cleanupCorruptData(pushDevices);
|
||||
expect(safePushDevices.length).to.equal(3);
|
||||
expect(safePushDevices[0].type).to.equal('android');
|
||||
expect(safePushDevices[0].regId).to.equal('1234');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { model as Tag } from '../../../../website/server/models/tag';
|
||||
|
||||
describe('Tag Model', () => {
|
||||
context('cleanupCorruptData', () => {
|
||||
it('converts an array of tags to a safe version', () => {
|
||||
const tags = [
|
||||
null, // invalid, not an object
|
||||
{ name: '123' }, // invalid, no id
|
||||
{ id: '123' }, // invalid, no name
|
||||
new Tag({ name: 'ABC', id: 123 }), // valid
|
||||
];
|
||||
|
||||
const safetags = Tag.cleanupCorruptData(tags);
|
||||
expect(safetags.length).to.equal(1);
|
||||
expect(safetags[0].name).to.equal('ABC');
|
||||
expect(safetags[0].id).to.equal('123');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -181,6 +181,146 @@ describe('User Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('post init', () => {
|
||||
it('removes invalid tags when loading the user', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
await user.update({
|
||||
$set: {
|
||||
tags: [
|
||||
null, // invalid, not an object
|
||||
// { name: '123' }, // invalid, no id - generated automatically
|
||||
{ id: '123' }, // invalid, no name
|
||||
{ name: 'ABC', id: '1234' }, // valid
|
||||
],
|
||||
},
|
||||
}).exec();
|
||||
|
||||
user = await User.findById(user._id).exec();
|
||||
|
||||
const userToJSON = user.toJSON();
|
||||
expect(userToJSON.tags.length).to.equal(1);
|
||||
|
||||
expect(userToJSON.tags[0]).to.have.all.keys(['id', 'name']);
|
||||
expect(userToJSON.tags[0].id).to.equal('1234');
|
||||
expect(userToJSON.tags[0].name).to.equal('ABC');
|
||||
});
|
||||
|
||||
it('removes invalid push devices when loading the user', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
await user.update({
|
||||
$set: {
|
||||
pushDevices: [
|
||||
null, // invalid, not an object
|
||||
{ regId: '123' }, // invalid, no type
|
||||
{ type: 'android' }, // invalid, no regId
|
||||
{ type: 'android', regId: '1234' }, // valid
|
||||
],
|
||||
},
|
||||
}).exec();
|
||||
|
||||
user = await User.findById(user._id).exec();
|
||||
|
||||
const userToJSON = user.toJSON();
|
||||
expect(userToJSON.pushDevices.length).to.equal(1);
|
||||
|
||||
expect(userToJSON.pushDevices[0]).to.have.all.keys(['regId', 'type', 'createdAt', 'updatedAt']);
|
||||
expect(userToJSON.pushDevices[0].type).to.equal('android');
|
||||
expect(userToJSON.pushDevices[0].regId).to.equal('1234');
|
||||
});
|
||||
|
||||
it('removes duplicate push devices when loading the user', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
await user.update({
|
||||
$set: {
|
||||
pushDevices: [
|
||||
{ type: 'android', regId: '1234' },
|
||||
{ type: 'android', regId: '1234' },
|
||||
],
|
||||
},
|
||||
}).exec();
|
||||
|
||||
user = await User.findById(user._id).exec();
|
||||
|
||||
const userToJSON = user.toJSON();
|
||||
expect(userToJSON.pushDevices.length).to.equal(1);
|
||||
|
||||
expect(userToJSON.pushDevices[0]).to.have.all.keys(['regId', 'type', 'createdAt', 'updatedAt']);
|
||||
expect(userToJSON.pushDevices[0].type).to.equal('android');
|
||||
expect(userToJSON.pushDevices[0].regId).to.equal('1234');
|
||||
});
|
||||
|
||||
it('removes invalid notifications when loading the user', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
await user.update({
|
||||
$set: {
|
||||
notifications: [
|
||||
null, // invalid, not an object
|
||||
{ seen: true }, // invalid, no type or id
|
||||
{ id: 123 }, // invalid, no type
|
||||
// invalid, no id, not included here because the id would be added automatically
|
||||
// {type: 'ABC'},
|
||||
{ type: 'ABC', id: '123' }, // valid
|
||||
],
|
||||
},
|
||||
}).exec();
|
||||
|
||||
user = await User.findById(user._id).exec();
|
||||
|
||||
const userToJSON = user.toJSON();
|
||||
expect(userToJSON.notifications.length).to.equal(1);
|
||||
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('ABC');
|
||||
expect(userToJSON.notifications[0].id).to.equal('123');
|
||||
});
|
||||
|
||||
it('removes multiple NEW_CHAT_MESSAGE for the same group', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
await user.update({
|
||||
$set: {
|
||||
notifications: [
|
||||
{
|
||||
type: 'NEW_CHAT_MESSAGE',
|
||||
id: 123,
|
||||
data: { group: { id: 12345 } },
|
||||
},
|
||||
{
|
||||
type: 'NEW_CHAT_MESSAGE',
|
||||
id: 1234,
|
||||
data: { group: { id: 12345 } },
|
||||
},
|
||||
{
|
||||
type: 'NEW_CHAT_MESSAGE',
|
||||
id: 123,
|
||||
data: { group: { id: 123456 } },
|
||||
}, // not duplicate, different group
|
||||
{
|
||||
type: 'NEW_CHAT_MESSAGE_DIFF',
|
||||
id: 123,
|
||||
data: { group: { id: 12345 } },
|
||||
}, // not duplicate, different type
|
||||
],
|
||||
},
|
||||
}).exec();
|
||||
|
||||
user = await User.findById(user._id).exec();
|
||||
|
||||
const userToJSON = user.toJSON();
|
||||
expect(userToJSON.notifications.length).to.equal(3);
|
||||
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('NEW_CHAT_MESSAGE');
|
||||
expect(userToJSON.notifications[0].id).to.equal('123');
|
||||
expect(userToJSON.notifications[0].data).to.deep.equal({ group: { id: 12345 } });
|
||||
expect(userToJSON.notifications[0].seen).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('notifications', () => {
|
||||
it('can add notifications without data', () => {
|
||||
const user = new User();
|
||||
@@ -195,26 +335,6 @@ describe('User Model', () => {
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
});
|
||||
|
||||
it('removes invalid notifications when calling toJSON', () => {
|
||||
const user = new User();
|
||||
|
||||
user.notifications = [
|
||||
null, // invalid, not an object
|
||||
{ seen: true }, // invalid, no type or id
|
||||
{ id: 123 }, // invalid, no type
|
||||
// invalid, no id, not included here because the id would be added automatically
|
||||
// {type: 'ABC'},
|
||||
{ type: 'ABC', id: '123' }, // valid
|
||||
];
|
||||
|
||||
const userToJSON = user.toJSON();
|
||||
expect(userToJSON.notifications.length).to.equal(1);
|
||||
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('ABC');
|
||||
expect(userToJSON.notifications[0].id).to.equal('123');
|
||||
});
|
||||
|
||||
it('can add notifications with data and already marked as seen', () => {
|
||||
const user = new User();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { model as UserNotification } from '../../../../website/server/models/userNotification';
|
||||
|
||||
describe('UserNotification Model', () => {
|
||||
context('convertNotificationsToSafeJson', () => {
|
||||
context('cleanupCorruptData', () => {
|
||||
it('converts an array of notifications to a safe version', () => {
|
||||
const notifications = [
|
||||
null, // invalid, not an object
|
||||
@@ -11,11 +11,44 @@ describe('UserNotification Model', () => {
|
||||
new UserNotification({ type: 'ABC', id: 123 }), // valid
|
||||
];
|
||||
|
||||
const notificationsToJSON = UserNotification.convertNotificationsToSafeJson(notifications);
|
||||
expect(notificationsToJSON.length).to.equal(1);
|
||||
expect(notificationsToJSON[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(notificationsToJSON[0].type).to.equal('ABC');
|
||||
expect(notificationsToJSON[0].id).to.equal('123');
|
||||
const safeNotifications = UserNotification.cleanupCorruptData(notifications);
|
||||
expect(safeNotifications.length).to.equal(1);
|
||||
expect(safeNotifications[0].data).to.deep.equal({});
|
||||
expect(safeNotifications[0].seen).to.equal(false);
|
||||
expect(safeNotifications[0].type).to.equal('ABC');
|
||||
expect(safeNotifications[0].id).to.equal('123');
|
||||
});
|
||||
|
||||
it('removes multiple NEW_CHAT_MESSAGE for the same group', () => {
|
||||
const notifications = [
|
||||
new UserNotification({
|
||||
type: 'NEW_CHAT_MESSAGE',
|
||||
id: 123,
|
||||
data: { group: { id: 12345 } },
|
||||
}),
|
||||
new UserNotification({
|
||||
type: 'NEW_CHAT_MESSAGE',
|
||||
id: 1234,
|
||||
data: { group: { id: 12345 } },
|
||||
}),
|
||||
new UserNotification({
|
||||
type: 'NEW_CHAT_MESSAGE',
|
||||
id: 123,
|
||||
data: { group: { id: 123456 } },
|
||||
}), // not duplicate, different group
|
||||
new UserNotification({
|
||||
type: 'NEW_CHAT_MESSAGE_DIFF',
|
||||
id: 123,
|
||||
data: { group: { id: 12345 } },
|
||||
}), // not duplicate, different type
|
||||
];
|
||||
|
||||
const safeNotifications = UserNotification.cleanupCorruptData(notifications);
|
||||
expect(safeNotifications.length).to.equal(3);
|
||||
expect(safeNotifications[0].data).to.deep.equal({ group: { id: 12345 } });
|
||||
expect(safeNotifications[0].seen).to.equal(false);
|
||||
expect(safeNotifications[0].type).to.equal('NEW_CHAT_MESSAGE');
|
||||
expect(safeNotifications[0].id).to.equal('123');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -187,8 +187,7 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
});
|
||||
|
||||
context('official challenge is present', () => {
|
||||
let publicGuild; let user; let officialChallenge; let challenge; let
|
||||
challenge2;
|
||||
let publicGuild; let user; let officialChallenge; let unofficialChallenges;
|
||||
|
||||
before(async () => {
|
||||
const { group, groupLeader } = await createAndPopulateGroup({
|
||||
@@ -214,10 +213,14 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
});
|
||||
await user.post(`/challenges/${officialChallenge._id}/join`);
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge2._id}/join`);
|
||||
// We add 10 extra challenges to test whether the official challenge
|
||||
// (the oldest) makes it to the front page.
|
||||
unofficialChallenges = [];
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const challenge = await generateChallenge(user, group); // eslint-disable-line
|
||||
await user.post(`/challenges/${challenge._id}/join`); // eslint-disable-line
|
||||
unofficialChallenges.push(challenge);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return official challenges first', async () => {
|
||||
@@ -230,18 +233,17 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
it('should return newest challenges first, after official ones', async () => {
|
||||
let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
let foundChallengeIndex = _.findIndex(challenges, { _id: challenge._id });
|
||||
expect(foundChallengeIndex).to.eql(2);
|
||||
|
||||
foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
unofficialChallenges.forEach((chal, index) => {
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: chal._id });
|
||||
expect(foundChallengeIndex).to.eql(10 - index);
|
||||
});
|
||||
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
|
||||
|
||||
foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -242,7 +242,7 @@ describe('GET challenges/user', () => {
|
||||
});
|
||||
|
||||
context('official challenge is present', () => {
|
||||
let user; let officialChallenge; let challenge; let challenge2; let
|
||||
let user; let officialChallenge; let unofficialChallenges; let
|
||||
publicGuild;
|
||||
|
||||
before(async () => {
|
||||
@@ -270,10 +270,14 @@ describe('GET challenges/user', () => {
|
||||
});
|
||||
await user.post(`/challenges/${officialChallenge._id}/join`);
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge._id}/join`);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
await user.post(`/challenges/${challenge2._id}/join`);
|
||||
// We add 10 extra challenges to test whether the official challenge
|
||||
// (the oldest) makes it to the front page.
|
||||
unofficialChallenges = [];
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const challenge = await generateChallenge(user, group); // eslint-disable-line
|
||||
await user.post(`/challenges/${challenge._id}/join`); // eslint-disable-line
|
||||
unofficialChallenges.push(challenge);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return official challenges first', async () => {
|
||||
@@ -284,20 +288,23 @@ describe('GET challenges/user', () => {
|
||||
});
|
||||
|
||||
it('should return newest challenges first, after official ones', async () => {
|
||||
let challenges = await user.get('/challenges/user');
|
||||
let challenges = await user.get('/challenges/user?page=0');
|
||||
|
||||
let foundChallengeIndex = _.findIndex(challenges, { _id: challenge._id });
|
||||
expect(foundChallengeIndex).to.eql(2);
|
||||
|
||||
foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
unofficialChallenges.forEach((chal, index) => {
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: chal._id });
|
||||
if (index === 0) {
|
||||
expect(foundChallengeIndex).to.eql(-1);
|
||||
} else {
|
||||
expect(foundChallengeIndex).to.eql(10 - index);
|
||||
}
|
||||
});
|
||||
|
||||
const newChallenge = await generateChallenge(user, publicGuild);
|
||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||
|
||||
challenges = await user.get('/challenges/user');
|
||||
|
||||
foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
const foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||
TAVERN_ID,
|
||||
} from '../../../../../website/server/models/group';
|
||||
import { CHAT_FLAG_FROM_SHADOW_MUTE } from '../../../../../website/common/script/constants';
|
||||
import { CHAT_FLAG_FROM_SHADOW_MUTE, MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants';
|
||||
import { getMatchesByWordArray } from '../../../../../website/server/libs/stringUtils';
|
||||
import bannedWords from '../../../../../website/server/libs/bannedWords';
|
||||
import guildsAllowingBannedWords from '../../../../../website/server/libs/guildsAllowingBannedWords';
|
||||
@@ -494,6 +494,7 @@ describe('POST /chat', () => {
|
||||
123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789.
|
||||
THIS PART WON'T BE IN THE MESSAGE (over 3000)
|
||||
`;
|
||||
expect(veryLongMessage.length > MAX_MESSAGE_LENGTH).to.equal(true);
|
||||
|
||||
const newMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: veryLongMessage });
|
||||
const groupMessages = await user.get(`/groups/${groupWithChat._id}/chat`);
|
||||
@@ -501,9 +502,27 @@ describe('POST /chat', () => {
|
||||
expect(newMessage.message.id).to.exist;
|
||||
expect(groupMessages[0].id).to.exist;
|
||||
|
||||
expect(newMessage.message.text.length).to.eql(3000);
|
||||
expect(newMessage.message.text.length).to.eql(MAX_MESSAGE_LENGTH);
|
||||
expect(newMessage.message.text).to.not.contain('MESSAGE');
|
||||
expect(groupMessages[0].text.length).to.eql(3000);
|
||||
expect(groupMessages[0].text.length).to.eql(MAX_MESSAGE_LENGTH);
|
||||
});
|
||||
|
||||
it('chat message with mentions - mention link should not count towards 3000 chars limit', async () => {
|
||||
const memberUsername = 'memberUsername';
|
||||
await member.update({ 'auth.local.username': memberUsername });
|
||||
|
||||
const messageWithMentions = `hi @${memberUsername} 123456789
|
||||
123456789 123456789 123456789 123456789 123456789 123456789 89 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 12345678 END.`;
|
||||
expect(messageWithMentions.length).to.equal(MAX_MESSAGE_LENGTH);
|
||||
const newMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: messageWithMentions });
|
||||
const groupMessages = await user.get(`/groups/${groupWithChat._id}/chat`);
|
||||
|
||||
const mentionLink = `[@${memberUsername}](/profile/${member._id})`;
|
||||
expect(newMessage.message.text).to.include(mentionLink);
|
||||
expect(newMessage.message.text).to.include(' END.');
|
||||
expect(newMessage.message.text.length)
|
||||
.to.eql(messageWithMentions.length - (`@${memberUsername}`).length + mentionLink.length);
|
||||
expect(groupMessages[0].text.length).to.eql(newMessage.message.text.length);
|
||||
});
|
||||
|
||||
it('creates a chat with user styles', async () => {
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('payments - stripe - #checkout', () => {
|
||||
await expect(user.post(endpoint, { id: 123 })).to.eventually.be.rejected.and.include({
|
||||
code: 401,
|
||||
error: 'Error',
|
||||
message: 'Invalid API Key provided: aaaabbbb********************1111',
|
||||
// message: 'Invalid API Key provided: aaaabbbb********************1111',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('PUT /user', () => {
|
||||
context('Sub-Level Protected Operations', () => {
|
||||
const protectedOperations = {
|
||||
'class stat': { 'stats.class': 'wizard' },
|
||||
'flags unless whitelisted': { 'flags.dropsEnabled': true },
|
||||
'flags unless whitelisted': { 'flags.chatRevoked': true },
|
||||
webhooks: { 'preferences.webhooks': [1, 2, 3] },
|
||||
sleep: { 'preferences.sleep': true },
|
||||
'disable classes': { 'preferences.disableClasses': true },
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('PUT /user', () => {
|
||||
context('Sub-Level Protected Operations', () => {
|
||||
const protectedOperations = {
|
||||
'class stat': { 'stats.class': 'wizard' },
|
||||
'flags unless whitelisted': { 'flags.dropsEnabled': true },
|
||||
'flags unless whitelisted': { 'flags.chatRevoked': true },
|
||||
webhooks: { 'preferences.webhooks': [1, 2, 3] },
|
||||
sleep: { 'preferences.sleep': true },
|
||||
'disable classes': { 'preferences.disableClasses': true },
|
||||
|
||||
@@ -15,6 +15,7 @@ describe('common.fns.randomDrop', () => {
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user._tmp = user._tmp ? user._tmp : {};
|
||||
user.items.eggs.Wolf = 0;
|
||||
task = generateTodo({ userId: user._id });
|
||||
predictableRandom = sandbox.stub().returns(0.5);
|
||||
});
|
||||
@@ -34,10 +35,17 @@ describe('common.fns.randomDrop', () => {
|
||||
|
||||
context('drops enabled', () => {
|
||||
beforeEach(() => {
|
||||
user.flags.dropsEnabled = true;
|
||||
task.priority = 100000;
|
||||
});
|
||||
|
||||
it('awards an egg and a hatching potion if user has never received any', () => {
|
||||
delete user.items.eggs.Wolf;
|
||||
randomDrop(user, { task, predictableRandom });
|
||||
|
||||
expect(user._tmp.firstDrops.egg).to.be.a.string;
|
||||
expect(user._tmp.firstDrops.hatchingPotion).to.be.a.string;
|
||||
});
|
||||
|
||||
it('does nothing if user.items.lastDrop.count is exceeded', () => {
|
||||
user.items.lastDrop.count = 100;
|
||||
randomDrop(user, { task, predictableRandom });
|
||||
@@ -46,7 +54,6 @@ describe('common.fns.randomDrop', () => {
|
||||
|
||||
it('drops something when the task is a todo', () => {
|
||||
expect(user._tmp).to.eql({});
|
||||
user.flags.dropsEnabled = true;
|
||||
predictableRandom.returns(0.1);
|
||||
|
||||
randomDrop(user, { task, predictableRandom });
|
||||
@@ -56,7 +63,6 @@ describe('common.fns.randomDrop', () => {
|
||||
it('drops something when the task is a habit', () => {
|
||||
task = generateHabit({ userId: user._id });
|
||||
expect(user._tmp).to.eql({});
|
||||
user.flags.dropsEnabled = true;
|
||||
predictableRandom.returns(0.1);
|
||||
|
||||
randomDrop(user, { task, predictableRandom });
|
||||
@@ -66,7 +72,6 @@ describe('common.fns.randomDrop', () => {
|
||||
it('drops something when the task is a daily', () => {
|
||||
task = generateDaily({ userId: user._id });
|
||||
expect(user._tmp).to.eql({});
|
||||
user.flags.dropsEnabled = true;
|
||||
predictableRandom.returns(0.1);
|
||||
|
||||
randomDrop(user, { task, predictableRandom });
|
||||
@@ -76,7 +81,6 @@ describe('common.fns.randomDrop', () => {
|
||||
it('drops something when the task is a reward', () => {
|
||||
task = generateReward({ userId: user._id });
|
||||
expect(user._tmp).to.eql({});
|
||||
user.flags.dropsEnabled = true;
|
||||
predictableRandom.returns(0.1);
|
||||
|
||||
randomDrop(user, { task, predictableRandom });
|
||||
|
||||
@@ -110,13 +110,6 @@ describe('common.fns.updateStats', () => {
|
||||
expect(user.stats.points).to.eql(10);
|
||||
});
|
||||
|
||||
it('add user notification when drops are enabled', () => {
|
||||
user.stats.lvl = 3;
|
||||
updateStats(user, { });
|
||||
expect(user.addNotification).to.be.calledOnce;
|
||||
expect(user.addNotification).to.be.calledWith('DROPS_ENABLED');
|
||||
});
|
||||
|
||||
it('add user notification when the user levels up', () => {
|
||||
const initialLvl = user.stats.lvl;
|
||||
updateStats(user, {
|
||||
@@ -131,7 +124,7 @@ describe('common.fns.updateStats', () => {
|
||||
it('add user notification when rebirth is enabled', () => {
|
||||
user.stats.lvl = 51;
|
||||
updateStats(user, { });
|
||||
expect(user.addNotification).to.be.calledTwice; // once is for drops enabled
|
||||
expect(user.addNotification).to.be.calledOnce;
|
||||
expect(user.addNotification).to.be.calledWith('REBIRTH_ENABLED');
|
||||
});
|
||||
|
||||
|
||||
@@ -173,7 +173,6 @@ describe('shared.ops.rebirth', () => {
|
||||
|
||||
it('resets a user\'s flags', () => {
|
||||
user.flags.itemsEnabled = true;
|
||||
user.flags.dropsEnabled = true;
|
||||
user.flags.classSelected = true;
|
||||
user.flags.rebirthEnabled = true;
|
||||
user.flags.levelDrops = { test: 'test' };
|
||||
@@ -181,7 +180,6 @@ describe('shared.ops.rebirth', () => {
|
||||
rebirth(user);
|
||||
|
||||
expect(user.flags.itemsEnabled).to.be.false;
|
||||
expect(user.flags.dropsEnabled).to.be.false;
|
||||
expect(user.flags.classSelected).to.be.false;
|
||||
expect(user.flags.rebirthEnabled).to.be.false;
|
||||
expect(user.flags.levelDrops).to.be.empty;
|
||||
|
||||
@@ -13,41 +13,41 @@
|
||||
"test:unit": "vue-cli-service test:unit --require ./tests/unit/helpers.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.2.2",
|
||||
"@vue/cli-plugin-eslint": "^4.2.2",
|
||||
"@vue/cli-plugin-router": "^4.2.2",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.2.2",
|
||||
"@vue/cli-service": "^4.2.2",
|
||||
"@storybook/addon-actions": "^5.3.13",
|
||||
"@storybook/addon-knobs": "^5.3.13",
|
||||
"@storybook/addon-links": "^5.3.13",
|
||||
"@storybook/addon-notes": "^5.3.13",
|
||||
"@storybook/vue": "^5.3.13",
|
||||
"@vue/cli-plugin-babel": "^4.2.3",
|
||||
"@vue/cli-plugin-eslint": "^4.2.3",
|
||||
"@vue/cli-plugin-router": "^4.2.3",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.2.3",
|
||||
"@vue/cli-service": "^4.2.3",
|
||||
"@storybook/addon-actions": "^5.3.14",
|
||||
"@storybook/addon-knobs": "^5.3.14",
|
||||
"@storybook/addon-links": "^5.3.14",
|
||||
"@storybook/addon-notes": "^5.3.14",
|
||||
"@storybook/vue": "^5.3.14",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"amplitude-js": "^5.9.0",
|
||||
"axios": "^0.19.2",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"bootstrap": "^4.4.1",
|
||||
"bootstrap-vue": "^2.4.2",
|
||||
"bootstrap-vue": "^2.6.1",
|
||||
"chai": "^4.1.2",
|
||||
"core-js": "^3.6.4",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-habitrpg": "^6.2.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
"eslint-plugin-vue": "^6.2.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"habitica-markdown": "^1.3.2",
|
||||
"hellojs": "^1.18.4",
|
||||
"inspectpack": "^4.3.1",
|
||||
"inspectpack": "^4.4.0",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": "^3.4.1",
|
||||
"lodash": "^4.17.15",
|
||||
"moment": "^2.24.0",
|
||||
"nconf": "^0.10.0",
|
||||
"sass": "^1.25.0",
|
||||
"sass": "^1.26.2",
|
||||
"sass-loader": "^8.0.2",
|
||||
"smartbanner.js": "^1.15.0",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"svg-inline-loader": "^0.8.2",
|
||||
"svg-url-loader": "^3.0.3",
|
||||
"svgo": "^1.3.2",
|
||||
"svgo-loader": "^2.2.1",
|
||||
@@ -56,11 +56,11 @@
|
||||
"vue": "^2.6.11",
|
||||
"vue-cli-plugin-storybook": "^0.6.1",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
"vue-router": "^3.1.5",
|
||||
"vue-router": "^3.1.6",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vue2-perfect-scrollbar": "^1.3.0",
|
||||
"vuedraggable": "^2.23.1",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^4.41.6"
|
||||
"webpack": "^4.42.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
.promo_achievement_CottonCandyPink {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -668px -444px;
|
||||
background-position: -328px -316px;
|
||||
width: 204px;
|
||||
height: 102px;
|
||||
}
|
||||
.promo_armoire_backgrounds_202002 {
|
||||
.promo_armoire_backgrounds_202003 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -277px;
|
||||
background-position: 0px -445px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_022020 {
|
||||
.promo_cosplay {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -668px -148px;
|
||||
background-position: 0px 0px;
|
||||
width: 623px;
|
||||
height: 167px;
|
||||
}
|
||||
.promo_hugabug_bundle {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -445px;
|
||||
width: 420px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_202003 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -624px -211px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_pi_day {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -593px;
|
||||
width: 273px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -873px -444px;
|
||||
background-position: -624px -359px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_valentines_2020 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -668px 0px;
|
||||
width: 309px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_valentines_potions {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -425px;
|
||||
width: 420px;
|
||||
height: 147px;
|
||||
}
|
||||
.scene_cake {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -277px;
|
||||
width: 204px;
|
||||
height: 102px;
|
||||
}
|
||||
.scene_dailies {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: 0px -168px;
|
||||
width: 327px;
|
||||
height: 276px;
|
||||
}
|
||||
.scene_gaining_achievement {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -328px 0px;
|
||||
background-position: -624px 0px;
|
||||
width: 339px;
|
||||
height: 210px;
|
||||
}
|
||||
.scene_shanaqui {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -668px -296px;
|
||||
background-position: -328px -168px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
|
||||
@@ -1,420 +1,552 @@
|
||||
.Pet-Wolf-IcySnow {
|
||||
.Pet-Whale-Skeleton {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -82px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Peppermint {
|
||||
.Pet-Whale-White {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -164px -400px;
|
||||
background-position: -492px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Rainbow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: 0px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Red {
|
||||
.Pet-Whale-Zombie {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -164px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-RoseQuartz {
|
||||
.Pet-Wolf-Amber {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: 0px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-RoyalPurple {
|
||||
.Pet-Wolf-Aquatic {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -82px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Ruby {
|
||||
.Pet-Wolf-Aurora {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -164px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shade {
|
||||
.Pet-Wolf-Base {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -246px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shadow {
|
||||
.Pet-Wolf-Bronze {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -246px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shimmer {
|
||||
.Pet-Wolf-Celestial {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: 0px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Silver {
|
||||
.Pet-Wolf-CottonCandyBlue {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -82px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Skeleton {
|
||||
.Pet-Wolf-CottonCandyPink {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -164px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Spooky {
|
||||
.Pet-Wolf-Cupid {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -246px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-StarryNight {
|
||||
.Pet-Wolf-Desert {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -328px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Sunshine {
|
||||
.Pet-Wolf-Ember {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -328px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Thunderstorm {
|
||||
.Pet-Wolf-Fairy {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -328px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Veggie {
|
||||
.Pet-Wolf-Floral {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: 0px 0px;
|
||||
background-position: 0px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Veteran {
|
||||
.Pet-Wolf-Frost {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -82px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Watery {
|
||||
.Pet-Wolf-Ghost {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -164px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-White {
|
||||
.Pet-Wolf-Glass {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -246px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Zombie {
|
||||
.Pet-Wolf-Glow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -328px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Base {
|
||||
.Pet-Wolf-Golden {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -410px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-CottonCandyBlue {
|
||||
.Pet-Wolf-Holly {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -410px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-CottonCandyPink {
|
||||
.Pet-Wolf-IcySnow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -410px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Desert {
|
||||
.Pet-Wolf-Peppermint {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -410px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Golden {
|
||||
.Pet-Wolf-Rainbow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -492px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Red {
|
||||
.Pet-Wolf-Red {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -492px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Shade {
|
||||
.Pet-Wolf-RoseQuartz {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -492px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Skeleton {
|
||||
.Pet-Wolf-RoyalPurple {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -492px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-White {
|
||||
.Pet-Wolf-Ruby {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: 0px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Zombie {
|
||||
.Pet-Wolf-Shade {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -82px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet_HatchingPotion_Amber {
|
||||
.Pet-Wolf-Shadow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -164px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shimmer {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -246px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Silver {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -328px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Skeleton {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -410px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Spooky {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -492px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-StarryNight {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Sunshine {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Thunderstorm {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Veggie {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Veteran {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Watery {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: 0px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-White {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -82px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Zombie {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -164px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Base {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -246px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-CottonCandyBlue {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -328px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-CottonCandyPink {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -410px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Desert {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: 0px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Golden {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Red {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -656px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Shade {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -656px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Skeleton {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -656px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-White {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -656px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Zombie {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -656px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet_HatchingPotion_Amber {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: 0px -600px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Aquatic {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -315px -400px;
|
||||
background-position: -552px -669px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Aurora {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -384px -400px;
|
||||
background-position: -69px -600px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Base {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -453px -400px;
|
||||
background-position: -138px -600px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Bronze {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: 0px -638px;
|
||||
background-position: -207px -600px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Celestial {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -69px -500px;
|
||||
background-position: -276px -600px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_CottonCandyBlue {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -138px -500px;
|
||||
background-position: -345px -600px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_CottonCandyPink {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -207px -500px;
|
||||
background-position: -414px -600px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Cupid {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -276px -500px;
|
||||
background-position: -483px -600px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Desert {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -345px -500px;
|
||||
background-position: -552px -600px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Ember {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -414px -500px;
|
||||
background-position: -621px -600px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Fairy {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -483px -500px;
|
||||
background-position: 0px -669px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Floral {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px 0px;
|
||||
background-position: -69px -669px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Frost {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px -69px;
|
||||
background-position: -138px -669px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Ghost {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px -138px;
|
||||
background-position: -207px -669px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Glass {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px -207px;
|
||||
background-position: -276px -669px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Glow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px -276px;
|
||||
background-position: -345px -669px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Golden {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px -345px;
|
||||
background-position: -414px -669px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Holly {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px -414px;
|
||||
background-position: -483px -669px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_IcySnow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -574px -483px;
|
||||
background-position: -656px -500px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Peppermint {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: 0px -569px;
|
||||
background-position: -621px -669px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Purple {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -69px -569px;
|
||||
background-position: -738px 0px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Rainbow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -138px -569px;
|
||||
background-position: -738px -69px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Red {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -207px -569px;
|
||||
background-position: -738px -138px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_RoseQuartz {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -276px -569px;
|
||||
background-position: -738px -207px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_RoyalPurple {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -345px -569px;
|
||||
background-position: -738px -276px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Ruby {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -414px -569px;
|
||||
background-position: -738px -345px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Shade {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -483px -569px;
|
||||
background-position: -738px -414px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Shadow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -552px -569px;
|
||||
background-position: -738px -483px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Shimmer {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -643px 0px;
|
||||
background-position: -738px -552px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Silver {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -643px -69px;
|
||||
background-position: -738px -621px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Skeleton {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -643px -138px;
|
||||
background-position: 0px -738px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Spooky {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -643px -207px;
|
||||
background-position: -69px -738px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_StarryNight {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -643px -276px;
|
||||
background-position: -138px -738px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Sunshine {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -643px -345px;
|
||||
background-position: -207px -738px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Thunderstorm {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -643px -414px;
|
||||
background-position: -276px -738px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Watery {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -643px -483px;
|
||||
background-position: -345px -738px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_White {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: -643px -552px;
|
||||
background-position: -414px -738px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Zombie {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-27.png');
|
||||
background-position: 0px -500px;
|
||||
background-position: -483px -738px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 475 KiB After Width: | Height: | Size: 473 KiB |
|
Before Width: | Height: | Size: 672 KiB After Width: | Height: | Size: 658 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 299 KiB After Width: | Height: | Size: 286 KiB |
|
Before Width: | Height: | Size: 348 KiB After Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 183 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 138 KiB |
@@ -23,7 +23,7 @@ input, textarea, input.form-control, textarea.form-control {
|
||||
border-radius: 2px;
|
||||
font-size: 14px;
|
||||
line-height: 1.43;
|
||||
color: $gray-200;
|
||||
color: $gray-50;
|
||||
border: 1px solid $gray-400;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
@@ -32,7 +32,6 @@ input, textarea, input.form-control, textarea.form-control {
|
||||
|
||||
&:active:not(:disabled), &:focus:not(:disabled) {
|
||||
border-color: $purple-500;
|
||||
color: $gray-50;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="drops-enabled"
|
||||
:title="$t('dropsEnabled')"
|
||||
size="lg"
|
||||
:hide-footer="true"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<div class="col-6 offset-3 text-center">
|
||||
<p></p>
|
||||
<div class="item-drop-icon Pet_Egg_Wolf"></div>
|
||||
<span v-html="firstDropText"></span>
|
||||
<p></p>
|
||||
<div class="item-drop-icon Pet_Currency_Gem"></div>
|
||||
<span v-html="$t('useGems')"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="col-12 text-center">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.item-drop-icon {
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
import * as eggs from '@/../../common/script/content/eggs';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
eggs,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
firstDropText () {
|
||||
return this.$t('firstDrop', {
|
||||
eggText: this.eggs.all.Wolf.text(),
|
||||
eggNotes: this.eggs.all.Wolf.notes(),
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'drops-enabled');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="first-drops"
|
||||
size="md"
|
||||
:hide-header="true"
|
||||
:hide-footer="true"
|
||||
>
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="modal-close"
|
||||
@click="close()"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon"
|
||||
v-html="icons.close"
|
||||
></div>
|
||||
</div>
|
||||
<h2
|
||||
v-once
|
||||
class="mt-3 mb-4"
|
||||
>
|
||||
{{ $t('foundNewItems') }}
|
||||
</h2>
|
||||
<div class="d-flex justify-content-center">
|
||||
<div
|
||||
class="item-box ml-auto mr-3"
|
||||
:class="eggClass"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="item-box mr-auto"
|
||||
:class="potionClass"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-once
|
||||
class="mt-4"
|
||||
>
|
||||
{{ $t('foundNewItemsExplanation') }}
|
||||
</p>
|
||||
<p
|
||||
v-once
|
||||
class="strong mb-4"
|
||||
>
|
||||
{{ $t('foundNewItemsCTA') }}
|
||||
</p>
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-primary mb-2"
|
||||
@click="toInventory()"
|
||||
>
|
||||
{{ $t('letsgo') }}
|
||||
</button>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#first-drops {
|
||||
.modal-body {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
margin-top: 15vh;
|
||||
width: 21rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
h2 {
|
||||
color: $purple-200;
|
||||
}
|
||||
|
||||
.item-box {
|
||||
background-color: $gray-600;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 4px;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
cursor: pointer;
|
||||
.svg-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import closeIcon from '@/assets/svg/close.svg';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
close: closeIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
eggClass () {
|
||||
return `Pet_Egg_${this.$store.state.firstDropsOptions.egg}`;
|
||||
},
|
||||
potionClass () {
|
||||
return `Pet_HatchingPotion_${this.$store.state.firstDropsOptions.hatchingPotion}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$store.state.firstDropsOptions = {
|
||||
egg: '',
|
||||
hatchingPotion: '',
|
||||
};
|
||||
this.$root.$emit('habitica::dismiss-modal', 'first-drops');
|
||||
},
|
||||
toInventory () {
|
||||
this.$router.push('/inventory/items');
|
||||
this.close();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -21,7 +21,8 @@
|
||||
<span v-if="challenge.leader === null">
|
||||
{{ $t('noChallengeOwner') }}
|
||||
</span>
|
||||
<user-link v-else
|
||||
<user-link
|
||||
v-else
|
||||
class="mx-1"
|
||||
:user="challenge.leader"
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<b-link
|
||||
<router-link
|
||||
v-if="group"
|
||||
@click.prevent="goToGroup"
|
||||
:to="toPath"
|
||||
>
|
||||
{{ group.name }}
|
||||
</b-link>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -12,15 +12,26 @@ import { TAVERN_ID } from '@/../../common/script/constants';
|
||||
|
||||
export default {
|
||||
props: ['group'],
|
||||
methods: {
|
||||
goToGroup () {
|
||||
if (this.group.type === 'party') {
|
||||
this.$router.push({ name: 'party' });
|
||||
} else if (this.group._id === TAVERN_ID) {
|
||||
this.$router.push({ name: 'tavern' });
|
||||
} else {
|
||||
this.$router.push({ name: 'guild', params: { groupId: this.group._id } });
|
||||
computed: {
|
||||
toPath () {
|
||||
if (this.group._id === TAVERN_ID) {
|
||||
return {
|
||||
name: 'tavern',
|
||||
};
|
||||
}
|
||||
|
||||
if (this.group.type === 'party') {
|
||||
return {
|
||||
name: 'party',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'guild',
|
||||
params: {
|
||||
groupId: this.group._id,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
v-model="newMessage"
|
||||
:placeholder="placeholder"
|
||||
:class="{'user-entry': newMessage}"
|
||||
maxlength="3000"
|
||||
:maxlength="MAX_MESSAGE_LENGTH"
|
||||
@keydown="updateCarretPosition"
|
||||
@keyup.ctrl.enter="sendMessageShortcut()"
|
||||
@keydown.tab="handleTab($event)"
|
||||
@@ -30,7 +30,7 @@
|
||||
@keydown.esc="handleEscape($event)"
|
||||
@paste="disableMessageSendShortcut()"
|
||||
></textarea>
|
||||
<span>{{ currentLength }} / 3000</span>
|
||||
<span>{{ currentLength }} / {{ MAX_MESSAGE_LENGTH }}</span>
|
||||
<autocomplete
|
||||
ref="autocomplete"
|
||||
:text="newMessage"
|
||||
@@ -91,6 +91,7 @@ import communityGuidelines from './communityGuidelines';
|
||||
import chatMessage from '../chat/chatMessages';
|
||||
import { mapState } from '@/libs/store';
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
import { MAX_MESSAGE_LENGTH } from '@/../../common/script/constants';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
@@ -116,6 +117,7 @@ export default {
|
||||
LEFT: 0,
|
||||
},
|
||||
textbox: this.$refs,
|
||||
MAX_MESSAGE_LENGTH: MAX_MESSAGE_LENGTH.toString(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -509,11 +509,7 @@ export default {
|
||||
// Load invites
|
||||
}
|
||||
await this.fetchGuild();
|
||||
// Fetch group members on load
|
||||
this.members = await this.loadMembers({
|
||||
groupId: this.group._id,
|
||||
includeAllPublicFields: true,
|
||||
});
|
||||
|
||||
this.$root.$on('updatedGroup', group => {
|
||||
const updatedGroup = extend(this.group, group);
|
||||
this.$set(this.group, updatedGroup);
|
||||
@@ -576,6 +572,11 @@ export default {
|
||||
this.$delete(this.user.newMessages, groupId);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
this.members = await this.loadMembers({
|
||||
groupId: this.group._id,
|
||||
includeAllPublicFields: true,
|
||||
});
|
||||
},
|
||||
hasUnreadMessages (groupId) {
|
||||
if (this.user.newMessages[groupId]) return true;
|
||||
|
||||
@@ -475,7 +475,7 @@ export default {
|
||||
return this.$store.state.memberModalOptions.challengeId;
|
||||
},
|
||||
sortedMembers () {
|
||||
let sortedMembers = this.members;
|
||||
let sortedMembers = this.members.slice(); // shallow clone to avoid infinite loop
|
||||
|
||||
if (!isEmpty(this.sortOption)) {
|
||||
// Use the memberlist filtered by searchTerm
|
||||
@@ -483,7 +483,13 @@ export default {
|
||||
// If members are to be sorted by name, use localeCompare for case-
|
||||
// insensitive sort
|
||||
sortedMembers.sort(
|
||||
(a, b) => a.profile.name.localeCompare(b.profile.name),
|
||||
(a, b) => {
|
||||
if (this.sortOption.direction === 'desc') {
|
||||
return b.profile.name.localeCompare(a.profile.name);
|
||||
}
|
||||
|
||||
return a.profile.name.localeCompare(b.profile.name);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
sortedMembers = orderBy(
|
||||
|
||||
@@ -170,7 +170,30 @@ export default {
|
||||
return Math.floor(this.currentWidth / 140) + 1;
|
||||
},
|
||||
sortedPartyMembers () {
|
||||
return orderBy(this.partyMembers, [this.user.party.order], [this.user.party.orderAscending]);
|
||||
let sortedMembers = this.partyMembers.slice(); // shallow clone to avoid infinite loop
|
||||
const { order, orderAscending } = this.user.party;
|
||||
|
||||
if (order === 'profile.name') {
|
||||
// If members are to be sorted by name, use localeCompare for case-
|
||||
// insensitive sort
|
||||
sortedMembers.sort(
|
||||
(a, b) => {
|
||||
if (orderAscending === 'desc') {
|
||||
return b.profile.name.localeCompare(a.profile.name);
|
||||
}
|
||||
|
||||
return a.profile.name.localeCompare(b.profile.name);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
sortedMembers = orderBy(
|
||||
sortedMembers,
|
||||
[order],
|
||||
[orderAscending],
|
||||
);
|
||||
}
|
||||
|
||||
return sortedMembers;
|
||||
},
|
||||
hideHeader () {
|
||||
return ['groupPlan', 'privateMessages'].includes(this.$route.name);
|
||||
|
||||
@@ -2,19 +2,38 @@
|
||||
<div
|
||||
class="notification d-flex flex-column justify-content-center text-center"
|
||||
>
|
||||
<strong class="mx-auto mb-2"> {{ $t('g1g1Announcement') }} </strong>
|
||||
<p class="mx-4"> {{ $t('g1g1Details') }} </p>
|
||||
<strong
|
||||
v-once
|
||||
class="mx-auto mb-2"
|
||||
>
|
||||
{{ $t('g1g1Announcement') }}
|
||||
</strong>
|
||||
<p
|
||||
v-once
|
||||
class="mx-4"
|
||||
>
|
||||
{{ $t('g1g1Details') }}
|
||||
</p>
|
||||
<div
|
||||
class="btn-secondary mx-auto d-flex"
|
||||
@click="showSelectUser()"
|
||||
>
|
||||
<div class="m-auto"> {{ $t('sendGift') }} </div>
|
||||
<div
|
||||
v-once
|
||||
class="m-auto"
|
||||
>
|
||||
{{ $t('sendGift') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="notification-remove"
|
||||
@click.stop="remove()"
|
||||
>
|
||||
<div class="svg-icon" v-html="icons.close"></div>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon"
|
||||
v-html="icons.close"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="username-notification-title">
|
||||
{{ $t('setUsernameNotificationTitle') }}
|
||||
</div>
|
||||
<div>{{ $t('setUsernameNotificationBody') }}</div>
|
||||
<div>{{ $t('changeUsernameDisclaimer') }}</div>
|
||||
<div class="current-username-container mx-auto">
|
||||
<label class="font-weight-bold">{{ $t('currentUsername') + " " }}</label>
|
||||
<label>@</label>
|
||||
|
||||
@@ -133,13 +133,12 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapState } from '@/libs/store';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import userIcon from '@/assets/svg/user.svg';
|
||||
import MenuDropdown from '../ui/customMenuDropdown';
|
||||
import markPMSRead from '@/../../common/script/ops/markPMSRead';
|
||||
import MessageCount from './messageCount';
|
||||
import { EVENTS } from '@/libs/events';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -164,11 +163,8 @@ export default {
|
||||
this.$root.$emit('bv::show::modal', 'avatar-modal');
|
||||
},
|
||||
showPrivateMessages () {
|
||||
markPMSRead(this.user);
|
||||
axios.post('/api/v4/user/mark-pms-read');
|
||||
|
||||
if (this.$router.history.current.name === 'privateMessages') {
|
||||
this.$root.$emit('pm::refresh');
|
||||
this.$root.$emit(EVENTS.PM_REFRESH);
|
||||
} else {
|
||||
this.$router.push('/private-messages');
|
||||
}
|
||||
|
||||
@@ -10,21 +10,7 @@
|
||||
:backer="backer"
|
||||
:contributor="contributor"
|
||||
:name="displayName"
|
||||
/><span
|
||||
v-if="username"
|
||||
class="username"
|
||||
>@{{ username }}</span>
|
||||
<div
|
||||
v-if="lastMessageDate"
|
||||
class="time"
|
||||
>
|
||||
{{ lastMessageDate | timeAgo }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-row">
|
||||
<div class="messagePreview">
|
||||
{{ lastMessageText }}
|
||||
</div>
|
||||
/>
|
||||
<div
|
||||
v-if="userLoggedIn.id !== uuid"
|
||||
class="actions"
|
||||
@@ -44,16 +30,33 @@
|
||||
v-html="icons.dots"
|
||||
></div>
|
||||
</template>
|
||||
<b-dropdown-item @click="block()">
|
||||
<b-dropdown-item @click="toggleBlock()">
|
||||
<span class="dropdown-icon-item">
|
||||
<div
|
||||
class="svg-icon inline"
|
||||
v-html="icons.remove"
|
||||
></div><span class="text">{{ $t('block') }}</span></span>
|
||||
></div><span class="text">{{ $t(isBlocked ? 'unblock' : 'block') }}</span></span>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<span class="username-row">
|
||||
<span
|
||||
v-if="username"
|
||||
class="username"
|
||||
>@{{ username }}</span> <span
|
||||
v-if="lastMessageDate"
|
||||
class="time"
|
||||
>•
|
||||
{{ lastMessageDate | timeAgo }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<div class="preview-row">
|
||||
<div class="messagePreview">
|
||||
{{ lastMessageText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -79,6 +82,9 @@ export default {
|
||||
...mapState({
|
||||
userLoggedIn: 'user.data',
|
||||
}),
|
||||
isBlocked () {
|
||||
return this.userLoggedIn.inbox.blocks.includes(this.uuid);
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -101,8 +107,8 @@ export default {
|
||||
dropdown.hide();
|
||||
}
|
||||
},
|
||||
block () {
|
||||
this.$store.dispatch('user:block', {
|
||||
toggleBlock () {
|
||||
this.$store.dispatch(this.isBlocked ? 'user:unblock' : 'user:block', {
|
||||
uuid: this.uuid,
|
||||
});
|
||||
},
|
||||
@@ -133,6 +139,12 @@ export default {
|
||||
height: 16px;
|
||||
width: 4px;
|
||||
|
||||
&:not(:hover) {
|
||||
svg path {
|
||||
fill: $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
svg path {
|
||||
fill: $purple-300
|
||||
}
|
||||
@@ -144,7 +156,7 @@ export default {
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.conversation {
|
||||
padding: 1.5rem;
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid $gray-500;
|
||||
|
||||
&:hover {
|
||||
@@ -164,6 +176,7 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
|
||||
.user-label {
|
||||
flex: 1;
|
||||
@@ -172,11 +185,6 @@ export default {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.username {
|
||||
flex: 1;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.time {
|
||||
flex: 2;
|
||||
text-align: end;
|
||||
@@ -187,10 +195,15 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.username-row {
|
||||
flex: 1;
|
||||
flex-grow: 0;
|
||||
font-size: 12px;
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
.messagePreview {
|
||||
//width: 100%;
|
||||
height: 30px;
|
||||
margin-right: 40px;
|
||||
max-height: 30px;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
@@ -221,7 +234,6 @@ export default {
|
||||
right: 0;
|
||||
display: none;
|
||||
width: 16px;
|
||||
margin-top: 4px;
|
||||
|
||||
.dots {
|
||||
height: 16px;
|
||||
|
||||
@@ -34,10 +34,9 @@
|
||||
class="d-flex flex-grow-1"
|
||||
>
|
||||
<avatar
|
||||
v-if="msg.userStyles || (cachedProfileData[msg.uuid]
|
||||
&& !cachedProfileData[msg.uuid].rejected)"
|
||||
v-if="conversationOpponentUser"
|
||||
class="avatar-left"
|
||||
:member="msg.userStyles || cachedProfileData[msg.uuid]"
|
||||
:member="conversationOpponentUser"
|
||||
:avatar-only="true"
|
||||
:override-top-padding="'14px'"
|
||||
:hide-class-badge="true"
|
||||
@@ -65,10 +64,9 @@
|
||||
/>
|
||||
</div>
|
||||
<avatar
|
||||
v-if="msg.userStyles
|
||||
|| (cachedProfileData[msg.uuid] && !cachedProfileData[msg.uuid].rejected)"
|
||||
v-if="user"
|
||||
class="avatar-right"
|
||||
:member="msg.userStyles || cachedProfileData[msg.uuid]"
|
||||
:member="user"
|
||||
:avatar-only="true"
|
||||
:hide-class-badge="true"
|
||||
:override-top-padding="'14px'"
|
||||
@@ -88,18 +86,18 @@
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 15%;
|
||||
width: 170px;
|
||||
min-width: 8rem;
|
||||
height: 120px;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.avatar-left {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
.avatar-right {
|
||||
margin-left: -1rem;
|
||||
|
||||
::v-deep .character-sprites {
|
||||
margin-right: 1rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -201,7 +199,6 @@
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import axios from 'axios';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { PerfectScrollbar } from 'vue2-perfect-scrollbar';
|
||||
import { mapState } from '@/libs/store';
|
||||
@@ -219,13 +216,11 @@ export default {
|
||||
chat: {},
|
||||
isLoading: Boolean,
|
||||
canLoadMore: Boolean,
|
||||
conversationOpponentUser: {},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
currentDayDividerDisplay: moment().day(),
|
||||
cachedProfileData: {},
|
||||
currentProfileLoadedCount: 0,
|
||||
currentProfileLoadedEnd: 10,
|
||||
loading: false,
|
||||
handleScrollBack: false,
|
||||
lastOffset: -1,
|
||||
@@ -233,8 +228,6 @@ export default {
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.loadProfileCache();
|
||||
|
||||
this.$el.addEventListener('selectstart', () => this.handleSelectStart());
|
||||
this.$el.addEventListener('mouseup', () => this.handleSelectChange());
|
||||
},
|
||||
@@ -253,7 +246,6 @@ export default {
|
||||
// @TODO: We need a different lazy load mechnism.
|
||||
// But honestly, adding a paging route to chat would solve this
|
||||
messages () {
|
||||
this.loadProfileCache();
|
||||
return this.chat;
|
||||
},
|
||||
psOptions () {
|
||||
@@ -263,9 +255,6 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleScroll () {
|
||||
this.loadProfileCache(window.scrollY / 1000);
|
||||
},
|
||||
async triggerLoad () {
|
||||
const container = this.$refs.container.$el;
|
||||
|
||||
@@ -284,62 +273,6 @@ export default {
|
||||
this.handleScrollBack = true;
|
||||
}
|
||||
},
|
||||
loadProfileCache: debounce(function loadProfileCache (screenPosition) {
|
||||
this._loadProfileCache(screenPosition);
|
||||
}, 1000),
|
||||
async _loadProfileCache (screenPosition) {
|
||||
if (this.loading) return;
|
||||
this.loading = true;
|
||||
|
||||
const promises = [];
|
||||
const noProfilesLoaded = Object.keys(this.cachedProfileData).length === 0;
|
||||
|
||||
// @TODO: write an explination
|
||||
// @TODO: Remove this after enough messages are cached
|
||||
if (!noProfilesLoaded && screenPosition
|
||||
&& Math.floor(screenPosition) + 1 > this.currentProfileLoadedEnd / 10) {
|
||||
this.currentProfileLoadedEnd = 10 * (Math.floor(screenPosition) + 1);
|
||||
} else if (!noProfilesLoaded && screenPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
const aboutToCache = {};
|
||||
this.messages.forEach(message => {
|
||||
const { uuid } = message;
|
||||
|
||||
if (message.userStyles) {
|
||||
this.$set(this.cachedProfileData, uuid, message.userStyles);
|
||||
}
|
||||
|
||||
if (Boolean(uuid) && !this.cachedProfileData[uuid] && !aboutToCache[uuid]) {
|
||||
if (uuid === 'system' || this.currentProfileLoadedCount === this.currentProfileLoadedEnd) return;
|
||||
aboutToCache[uuid] = {};
|
||||
promises.push(axios.get(`/api/v4/members/${uuid}`));
|
||||
this.currentProfileLoadedCount += 1;
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
results.forEach(result => {
|
||||
// We could not load the user. Maybe they were deleted.
|
||||
// So, let's cache empty so we don't try again
|
||||
if (!result || !result.data || result.status >= 400) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userData = result.data.data;
|
||||
this.$set(this.cachedProfileData, userData._id, userData);
|
||||
});
|
||||
|
||||
// Merge in any attempts that were rejected so we don't attempt again
|
||||
for (const uuid in aboutToCache) {
|
||||
if (!this.cachedProfileData[uuid]) {
|
||||
this.$set(this.cachedProfileData, uuid, { rejected: true });
|
||||
}
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
displayDivider (message) {
|
||||
if (this.currentDayDividerDisplay !== moment(message.timestamp).day()) {
|
||||
this.currentDayDividerDisplay = moment(message.timestamp).day();
|
||||
@@ -348,29 +281,8 @@ export default {
|
||||
|
||||
return false;
|
||||
},
|
||||
async showMemberModal (memberId) {
|
||||
let profile = this.cachedProfileData[memberId];
|
||||
|
||||
if (!profile._id) {
|
||||
const result = await this.$store.dispatch('members:fetchMember', { memberId });
|
||||
if (result.response && result.response.status === 404) {
|
||||
return this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: this.$t('messageDeletedUser'),
|
||||
type: 'error',
|
||||
timeout: false,
|
||||
});
|
||||
}
|
||||
this.cachedProfileData[memberId] = result.data.data;
|
||||
profile = result.data.data;
|
||||
}
|
||||
|
||||
// Open the modal only if the data is available
|
||||
if (profile && !profile.rejected) {
|
||||
this.$router.push({ name: 'userProfile', params: { userId: profile._id } });
|
||||
}
|
||||
|
||||
return null;
|
||||
showMemberModal (memberId) {
|
||||
this.$router.push({ name: 'userProfile', params: { userId: memberId } });
|
||||
},
|
||||
itemWasMounted: debounce(function itemWasMounted () {
|
||||
if (this.handleScrollBack) {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<testing />
|
||||
<testingletiant />
|
||||
<rebirth-enabled />
|
||||
<drops-enabled />
|
||||
<contributor />
|
||||
<won-challenge />
|
||||
<ultimate-gear />
|
||||
@@ -34,6 +33,7 @@
|
||||
<lost-masterclasser />
|
||||
<mind-over-matter />
|
||||
<onboarding-complete />
|
||||
<first-drops />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -128,7 +128,6 @@ import questInvitation from './achievements/questInvitation';
|
||||
import testing from './achievements/testing';
|
||||
import testingletiant from './achievements/testingletiant';
|
||||
import rebirthEnabled from './achievements/rebirthEnabled';
|
||||
import dropsEnabled from './achievements/dropsEnabled';
|
||||
import contributor from './achievements/contributor';
|
||||
import invitedFriend from './achievements/invitedFriend';
|
||||
import joinedChallenge from './achievements/joinedChallenge';
|
||||
@@ -144,6 +143,7 @@ import mindOverMatter from './achievements/mindOverMatter';
|
||||
import loginIncentives from './achievements/login-incentives';
|
||||
import onboardingComplete from './achievements/onboardingComplete';
|
||||
import verifyUsername from './settings/verifyUsername';
|
||||
import firstDrops from './achievements/firstDrops';
|
||||
|
||||
const NOTIFICATIONS = {
|
||||
CHALLENGE_JOINED_ACHIEVEMENT: {
|
||||
@@ -310,6 +310,14 @@ const NOTIFICATIONS = {
|
||||
achievement: 'rosyOutlook', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_BUG_BONANZA: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('achievementBugBonanza')}`,
|
||||
modalId: 'generic-achievement',
|
||||
data: {
|
||||
achievement: 'bugBonanza', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
@@ -333,7 +341,6 @@ export default {
|
||||
testing,
|
||||
testingletiant,
|
||||
rebirthEnabled,
|
||||
dropsEnabled,
|
||||
contributor,
|
||||
loginIncentives,
|
||||
verifyUsername,
|
||||
@@ -342,12 +349,12 @@ export default {
|
||||
mindOverMatter,
|
||||
justAddWater,
|
||||
onboardingComplete,
|
||||
firstDrops,
|
||||
},
|
||||
mixins: [notifications, guide],
|
||||
data () {
|
||||
// Levels that already display modals and should not trigger generic Level Up
|
||||
const unlockLevels = {
|
||||
3: 'drop system',
|
||||
10: 'class system',
|
||||
50: 'Orb of Rebirth',
|
||||
};
|
||||
@@ -361,7 +368,7 @@ export default {
|
||||
const handledNotifications = {};
|
||||
|
||||
[
|
||||
'GUILD_PROMPT', 'DROPS_ENABLED', 'REBIRTH_ENABLED', 'WON_CHALLENGE', 'STREAK_ACHIEVEMENT',
|
||||
'GUILD_PROMPT', 'REBIRTH_ENABLED', 'WON_CHALLENGE', 'STREAK_ACHIEVEMENT',
|
||||
'ULTIMATE_GEAR_ACHIEVEMENT', 'REBIRTH_ACHIEVEMENT', 'GUILD_JOINED_ACHIEVEMENT',
|
||||
'CHALLENGE_JOINED_ACHIEVEMENT', 'INVITED_FRIEND_ACHIEVEMENT', 'NEW_CONTRIBUTOR_LEVEL',
|
||||
'CRON', 'SCORED_TASK', 'LOGIN_INCENTIVE', 'ACHIEVEMENT_ALL_YOUR_BASE', 'ACHIEVEMENT_BACK_TO_BASICS',
|
||||
@@ -369,7 +376,7 @@ export default {
|
||||
'ACHIEVEMENT_MOUNT_MASTER', 'ACHIEVEMENT_TRIAD_BINGO', 'ACHIEVEMENT_DUST_DEVIL', 'ACHIEVEMENT_ARID_AUTHORITY',
|
||||
'ACHIEVEMENT_MONSTER_MAGUS', 'ACHIEVEMENT_UNDEAD_UNDERTAKER', 'ACHIEVEMENT_PRIMED_FOR_PAINTING',
|
||||
'ACHIEVEMENT_PEARLY_PRO', 'ACHIEVEMENT_TICKLED_PINK', 'ACHIEVEMENT_ROSY_OUTLOOK', 'ACHIEVEMENT',
|
||||
'ONBOARDING_COMPLETE',
|
||||
'ONBOARDING_COMPLETE', 'FIRST_DROPS', 'ACHIEVEMENT_BUG_BONANZA',
|
||||
].forEach(type => {
|
||||
handledNotifications[type] = true;
|
||||
});
|
||||
@@ -732,6 +739,13 @@ export default {
|
||||
|
||||
// @TODO: Use factory function instead
|
||||
switch (notification.type) { // eslint-disable-line default-case
|
||||
case 'FIRST_DROPS':
|
||||
if (notification.data) {
|
||||
this.$store.state.firstDropsOptions.egg = notification.data.egg;
|
||||
this.$store.state.firstDropsOptions.hatchingPotion = notification.data.hatchingPotion;
|
||||
this.$root.$emit('bv::show::modal', 'first-drops');
|
||||
}
|
||||
break;
|
||||
case 'GUILD_PROMPT':
|
||||
// @TODO: I'm pretty sure we can find better names for these
|
||||
if (notification.data.textletiant === -1) {
|
||||
@@ -740,9 +754,6 @@ export default {
|
||||
this.$root.$emit('bv::show::modal', 'testingletiant');
|
||||
}
|
||||
break;
|
||||
case 'DROPS_ENABLED':
|
||||
this.$root.$emit('bv::show::modal', 'drops-enabled');
|
||||
break;
|
||||
case 'REBIRTH_ENABLED':
|
||||
this.$root.$emit('bv::show::modal', 'rebirth-enabled');
|
||||
break;
|
||||
@@ -779,6 +790,7 @@ export default {
|
||||
case 'ACHIEVEMENT_PEARLY_PRO':
|
||||
case 'ACHIEVEMENT_TICKLED_PINK':
|
||||
case 'ACHIEVEMENT_ROSY_OUTLOOK':
|
||||
case 'ACHIEVEMENT_BUG_BONANZA':
|
||||
case 'GENERIC_ACHIEVEMENT':
|
||||
this.showNotificationWithModal(notification);
|
||||
break;
|
||||
|
||||