Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e31ce11052 | |||
| c4688b77fd | |||
| 9ab79ef225 | |||
| 7e4c8938e5 | |||
| 604b866113 | |||
| c0afae1e84 | |||
| 61dc94ada0 | |||
| f71ee7be18 | |||
| 8935885637 | |||
| 1ebda8281b | |||
| eae8ed946f | |||
| d7fe140012 | |||
| c317d1d040 | |||
| d63aafc7f1 | |||
| 08b2be684b | |||
| 4c653aa511 | |||
| 0896837528 | |||
| 8a1c1af461 | |||
| db7d1d0e14 | |||
| 6253359d31 | |||
| ce4bbe5552 | |||
| 3a4abac8b0 | |||
| 44eb245184 | |||
| 050fd15c97 | |||
| e1f1a264b4 | |||
| 3bfcc4a605 | |||
| 4beac98383 | |||
| 4da794271d | |||
| b18eb0d3f8 | |||
| 79f6f47b16 | |||
| 6ff8254137 | |||
| 1c00d7de5b | |||
| 61d970db86 | |||
| b911ecdcf0 | |||
| 74d0b25c54 | |||
| 830c4fb926 | |||
| 8aad136622 | |||
| 8d80792093 | |||
| 79f83d87d0 | |||
| c9bf157c23 | |||
| 3be42e9545 | |||
| 8a7b8f92d4 | |||
| e54425f947 | |||
| d953ea14da | |||
| 0c179fee2f | |||
| 05e229ccb0 | |||
| b29d937806 | |||
| 670b6a1563 | |||
| 44ded945dd | |||
| 08f1e2b273 | |||
| ca80f4ee33 | |||
| 65cbee9e75 | |||
| 0f8563c14e | |||
| c4117f99ed | |||
| 27aca19c8c | |||
| 80c18ffadd | |||
| bf2c4eb501 | |||
| ab1828c914 | |||
| b32f79f682 | |||
| 7b69289069 | |||
| a1fb80868f | |||
| 9d4fb80d15 | |||
| 43392f4952 | |||
| 6e46794822 | |||
| 43cad86201 | |||
| 734eae64a5 | |||
| 032c95d5c8 | |||
| b40411e219 | |||
| 86410f6bb7 | |||
| 8d2ada1463 | |||
| 95a3c932c5 | |||
| 51d1f6b86a | |||
| 809bacd10b | |||
| b0f29211a8 | |||
| 26f5bf554e | |||
| a1ec42c0b2 | |||
| 8674ea55f9 | |||
| c421da34cc | |||
| fb79b9e128 | |||
| 375b6719e9 | |||
| c9bed96077 | |||
| 1ccff7f1dd | |||
| 36461cbbdf | |||
| 85d290a1fa | |||
| 1bc756ee93 | |||
| ea44af0929 | |||
| 739963762e | |||
| b02ae35b28 | |||
| ace8e2f0bd | |||
| e90175b5d6 | |||
| 403fa18511 | |||
| ebd22296df | |||
| 613895e294 | |||
| 26437e7e2e |
@@ -10,6 +10,7 @@ dist/
|
||||
dist-client/
|
||||
apidoc_build/
|
||||
content_cache/
|
||||
i18n_cache/
|
||||
node_modules/
|
||||
|
||||
# Old migrations, disabled
|
||||
|
||||
@@ -8,13 +8,8 @@
|
||||
|
||||
# Requesting a feature
|
||||
|
||||
Habitica uses [Trello](https://trello.com/b/EpoYEYod/habitica) to track feature requests. [Read more](https://trello.com/c/odmhIqyW/440-read-first-table-of-contents).
|
||||
Habitica uses [this Google form](https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link) to track feature requests. Please post there rather than creating an issue.
|
||||
|
||||
# Contributing Code
|
||||
|
||||
See [Contributing to Habitica](http://habitica.fandom.com/wiki/Contributing_to_Habitica#Coders_.28Web_.26_Mobile.29)
|
||||
|
||||
## Issue Triage [](https://www.codetriage.com/habitrpg/habitica)
|
||||
|
||||
You can triage issues which may include reproducing bug reports or asking for vital information, such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to [subscribe to habitrpg on CodeTriage](https://www.codetriage.com/habitrpg/habitica).
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
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)
|
||||
Habitica  [](https://codeclimate.com/github/HabitRPG/habitrpg) [](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE)
|
||||
===============
|
||||
|
||||
[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.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.142.0",
|
||||
"version": "4.144.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.9.6",
|
||||
"@babel/preset-env": "^7.9.6",
|
||||
"@babel/register": "^7.9.0",
|
||||
"@google-cloud/trace-agent": "^4.2.5",
|
||||
"@babel/core": "^7.10.2",
|
||||
"@babel/preset-env": "^7.10.2",
|
||||
"@babel/register": "^7.10.1",
|
||||
"@google-cloud/trace-agent": "^5.0.0",
|
||||
"@slack/client": "^4.12.0",
|
||||
"accepts": "^1.3.5",
|
||||
"amazon-payments": "^0.2.8",
|
||||
@@ -46,9 +46,9 @@
|
||||
"lodash": "^4.17.15",
|
||||
"merge-stream": "^2.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.25.3",
|
||||
"moment": "^2.26.0",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.9.13",
|
||||
"mongoose": "^5.9.18",
|
||||
"morgan": "^1.10.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^1.0.2",
|
||||
@@ -112,7 +112,7 @@
|
||||
"expect.js": "^0.3.1",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^7.2.0",
|
||||
"monk": "^7.3.0",
|
||||
"require-again": "^2.0.0",
|
||||
"sinon": "^7.2.4",
|
||||
"sinon-chai": "^3.5.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
import highlightMentions from '../../../../website/server/libs/highlightMentions';
|
||||
|
||||
describe('highlightMentions', () => {
|
||||
@@ -100,6 +101,29 @@ describe('highlightMentions', () => {
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('http://www.medium.com/@user/blog [@user](/profile/111)');
|
||||
});
|
||||
|
||||
// https://spec.commonmark.org/0.29/#example-483
|
||||
it('doesn\'t highlight user in a link without url', async () => {
|
||||
const text = '[@user2]()';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
|
||||
// https://github.com/HabitRPG/habitica/issues/12217
|
||||
it('doesn\'t highlight user in link with url-escapable characters', async () => {
|
||||
const text = '[test](https://habitica.fandom.com/ru/@wiki/Снаряжение)';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
|
||||
// https://github.com/HabitRPG/habitica/issues/12223
|
||||
it('matches a link in between two the same links', async () => {
|
||||
const text = '[here](http://habitica.wikia.com/wiki/The_Keep:Pirate_Cove/FAQ)\n@user\n[hier](http://habitica.wikia.com/wiki/The_Keep:Pirate_Cove/FAQ)';
|
||||
|
||||
const result = await highlightMentions(text);
|
||||
|
||||
expect(result[0]).to.equal('[here](http://habitica.wikia.com/wiki/The_Keep:Pirate_Cove/FAQ)\n[@user](/profile/111)\n[hier](http://habitica.wikia.com/wiki/The_Keep:Pirate_Cove/FAQ)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exceptions in code blocks', () => {
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"test": {
|
||||
plugins: [
|
||||
["istanbul"],
|
||||
],
|
||||
},
|
||||
},
|
||||
"presets": [
|
||||
["es2015", { modules: false }],
|
||||
],
|
||||
"plugins": [
|
||||
"transform-object-rest-spread",
|
||||
],
|
||||
"comments": false,
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/* eslint-disable */
|
||||
// TODO verify if it's needed, added because Axios require Promise in the global scope
|
||||
// and babel-runtime doesn't affect external libraries
|
||||
require('babel-polyfill');
|
||||
|
||||
// Automatically setup SinonJS' sandbox for each test
|
||||
beforeEach(() => {
|
||||
global.sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.sandbox.restore();
|
||||
});
|
||||
|
||||
// require all test files
|
||||
const testsContext = require.context('./specs', true, /\.js$/);
|
||||
testsContext.keys().forEach(testsContext);
|
||||
|
||||
// require all .vue and .js files except main.js for coverage.
|
||||
const srcContext = require.context('../../../website/client', true, /^\.\/(?=(?!main(\.js)?$))(?=(.*\.(vue|js)$))/);
|
||||
srcContext.keys().forEach(srcContext);
|
||||
@@ -1,40 +0,0 @@
|
||||
/* eslint-disable */
|
||||
// This is a karma config file. For more details see
|
||||
// http://karma-runner.github.io/0.13/config/configuration-file.html
|
||||
// we are also using it with karma-webpack
|
||||
// https://github.com/webpack/karma-webpack
|
||||
|
||||
// Necessary for babel to respect the env version of .babelrc which is necessary
|
||||
// Because inject-loader does not work with ["es2015", { modules: false }] that we use
|
||||
// in order to let webpack2 handle the imports
|
||||
process.env.CHROME_BIN = require('puppeteer').executablePath();
|
||||
// eslint-disable-line no-process-env
|
||||
process.env.BABEL_ENV = process.env.NODE_ENV; // eslint-disable-line no-process-env
|
||||
const webpackConfig = require('../../../webpack/webpack.test.conf');
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
// to run in additional browsers:
|
||||
// 1. install corresponding karma launcher
|
||||
// http://karma-runner.github.io/0.13/config/browsers.html
|
||||
// 2. add it to the `browsers` array below.
|
||||
browsers: ['ChromeHeadless'],
|
||||
frameworks: ['mocha', 'sinon-stub-promise', 'sinon-chai', 'chai-as-promised', 'chai'],
|
||||
reporters: ['spec', 'coverage'],
|
||||
files: ['./index.js'],
|
||||
preprocessors: {
|
||||
'./index.js': ['webpack', 'sourcemap'],
|
||||
},
|
||||
webpack: webpackConfig,
|
||||
webpackMiddleware: {
|
||||
noInfo: true,
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: '../../../coverage/client-unit',
|
||||
reporters: [
|
||||
{ type: 'lcov', subdir: '.' },
|
||||
{ type: 'text-summary' },
|
||||
],
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -88,7 +88,14 @@ describe('shared.ops.buyMysterySet', () => {
|
||||
expect(user.items.gear.owned).to.have.property('armor_mystery_301404', true);
|
||||
expect(user.items.gear.owned).to.have.property('head_mystery_301404', true);
|
||||
expect(user.items.gear.owned).to.have.property('eyewear_mystery_301404', true);
|
||||
expect(analytics.track).to.be.called;
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
expect(analytics.track).to.be.calledWithMatch('acquire item', {
|
||||
uuid: user._id,
|
||||
itemKey: '301404',
|
||||
itemType: 'Subscriber Gear',
|
||||
acquireMethod: 'Hourglass',
|
||||
category: 'behavior',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import get from 'lodash/get';
|
||||
import unlock from '../../../website/common/script/ops/unlock';
|
||||
import i18n from '../../../website/common/script/i18n';
|
||||
import { generateUser } from '../../helpers/common.helper';
|
||||
@@ -8,8 +9,8 @@ describe('shared.ops.unlock', () => {
|
||||
const unlockPath = 'shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie';
|
||||
const unlockGearSetPath = 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars';
|
||||
const backgroundUnlockPath = 'background.giant_florals';
|
||||
const unlockCost = 1.25;
|
||||
const usersStartingGems = 5;
|
||||
const backgroundSetUnlockPath = 'background.archery_range,background.giant_florals,background.rainbows_end';
|
||||
const usersStartingGems = 50 / 4;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
@@ -48,32 +49,100 @@ describe('shared.ops.unlock', () => {
|
||||
});
|
||||
|
||||
it('returns an error when user already owns a full set', done => {
|
||||
let expectedBalance;
|
||||
|
||||
try {
|
||||
unlock(user, { query: { path: unlockPath } });
|
||||
expectedBalance = user.balance;
|
||||
unlock(user, { query: { path: unlockPath } });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('alreadyUnlocked'));
|
||||
expect(user.balance).to.equal(3.75);
|
||||
expect(user.balance).to.equal(expectedBalance);
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an error when user already owns a full set of gear', done => {
|
||||
let expectedBalance;
|
||||
|
||||
try {
|
||||
unlock(user, { query: { path: unlockGearSetPath } });
|
||||
expectedBalance = user.balance;
|
||||
unlock(user, { query: { path: unlockGearSetPath } });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('alreadyUnlocked'));
|
||||
expect(user.balance).to.equal(3.75);
|
||||
expect(user.balance).to.equal(expectedBalance);
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
xit('returns an error when user already owns items in a full set', done => {
|
||||
it('returns an error if an item does not exists', done => {
|
||||
try {
|
||||
unlock(user, { query: { path: unlockPath.split(',').splice(2).join(',') } });
|
||||
unlock(user, { query: { path: 'background.invalid_background' } });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an error if there are items from multiple sets', done => {
|
||||
try {
|
||||
unlock(user, { query: { path: 'shirt.convict,skin.0ff591' } });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an error if gear is not from the animal set', done => {
|
||||
try {
|
||||
unlock(user, { query: { path: 'items.gear.owned.back_mystery_202004' } });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an error if the item is free', done => {
|
||||
try {
|
||||
unlock(user, { query: { path: 'shirt.black' } });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an error if an item does not belong to a set (appearances)', done => {
|
||||
try {
|
||||
unlock(user, { query: { path: 'shirt.pink' } });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an error when user already owns items in a full set and it would be more expensive to buy the entire set', done => {
|
||||
try {
|
||||
// There are 11 shirts in the set, each cost 2 gems, the full set 5 gems
|
||||
// In order for the full purchase not to be worth, we must own 9
|
||||
const partialUnlockPaths = unlockPath.split(',');
|
||||
unlock(user, { query: { path: partialUnlockPaths[0] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[1] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[2] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[3] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[4] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[5] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[6] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[7] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[8] } });
|
||||
|
||||
unlock(user, { query: { path: unlockPath } });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
@@ -82,6 +151,22 @@ describe('shared.ops.unlock', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('does not return an error when user already owns items in a full set and it would not be more expensive to buy the entire set', () => {
|
||||
// There are 11 shirts in the set, each cost 2 gems, the full set 5 gems
|
||||
// In order for the full purchase to be worth, we can own already 8
|
||||
const partialUnlockPaths = unlockPath.split(',');
|
||||
unlock(user, { query: { path: partialUnlockPaths[0] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[1] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[2] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[3] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[4] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[5] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[6] } });
|
||||
unlock(user, { query: { path: partialUnlockPaths[7] } });
|
||||
|
||||
unlock(user, { query: { path: unlockPath } });
|
||||
});
|
||||
|
||||
it('equips an item already owned', () => {
|
||||
expect(user.purchased.background.giant_florals).to.not.exist;
|
||||
|
||||
@@ -107,31 +192,78 @@ describe('shared.ops.unlock', () => {
|
||||
expect(user.preferences.background).to.equal('');
|
||||
});
|
||||
|
||||
it('unlocks a full set', () => {
|
||||
it('unlocks a full set of appearance items', () => {
|
||||
const initialShirts = Object.keys(user.purchased.shirt).length;
|
||||
const [, message] = unlock(user, { query: { path: unlockPath } });
|
||||
|
||||
expect(message).to.equal(i18n.t('unlocked'));
|
||||
expect(user.purchased.shirt.convict).to.be.true;
|
||||
const individualPaths = unlockPath.split(',');
|
||||
individualPaths.forEach(path => {
|
||||
expect(get(user.purchased, path)).to.be.true;
|
||||
});
|
||||
expect(Object.keys(user.purchased.shirt).length)
|
||||
.to.equal(initialShirts + individualPaths.length);
|
||||
expect(user.balance).to.equal(usersStartingGems - 1.25);
|
||||
});
|
||||
|
||||
it('unlocks a full set of gear', () => {
|
||||
const initialGear = Object.keys(user.items.gear.owned).length;
|
||||
const [, message] = unlock(user, { query: { path: unlockGearSetPath } });
|
||||
|
||||
expect(message).to.equal(i18n.t('unlocked'));
|
||||
expect(user.items.gear.owned.headAccessory_special_wolfEars).to.be.true;
|
||||
|
||||
const individualPaths = unlockGearSetPath.split(',');
|
||||
individualPaths.forEach(path => {
|
||||
expect(get(user, path)).to.be.true;
|
||||
});
|
||||
expect(Object.keys(user.items.gear.owned).length)
|
||||
.to.equal(initialGear + individualPaths.length);
|
||||
expect(user.balance).to.equal(usersStartingGems - 1.25);
|
||||
});
|
||||
|
||||
it('unlocks an item', () => {
|
||||
it('unlocks a full set of backgrounds', () => {
|
||||
const initialBackgrounds = Object.keys(user.purchased.background).length;
|
||||
const [, message] = unlock(user, { query: { path: backgroundSetUnlockPath } });
|
||||
|
||||
expect(message).to.equal(i18n.t('unlocked'));
|
||||
const individualPaths = backgroundSetUnlockPath.split(',');
|
||||
individualPaths.forEach(path => {
|
||||
expect(get(user.purchased, path)).to.be.true;
|
||||
});
|
||||
expect(Object.keys(user.purchased.background).length)
|
||||
.to.equal(initialBackgrounds + individualPaths.length);
|
||||
expect(user.balance).to.equal(usersStartingGems - 3.75);
|
||||
});
|
||||
|
||||
it('unlocks an item (appearance)', () => {
|
||||
const path = unlockPath.split(',')[0];
|
||||
const initialShirts = Object.keys(user.purchased.shirt).length;
|
||||
const [, message] = unlock(user, { query: { path } });
|
||||
|
||||
expect(message).to.equal(i18n.t('unlocked'));
|
||||
expect(Object.keys(user.purchased.shirt).length).to.equal(initialShirts + 1);
|
||||
expect(get(user.purchased, path)).to.be.true;
|
||||
expect(user.balance).to.equal(usersStartingGems - 0.5);
|
||||
});
|
||||
|
||||
it('unlocks an item (gear)', () => {
|
||||
const path = unlockGearSetPath.split(',')[0];
|
||||
const initialGear = Object.keys(user.items.gear.owned).length;
|
||||
const [, message] = unlock(user, { query: { path } });
|
||||
|
||||
expect(message).to.equal(i18n.t('unlocked'));
|
||||
expect(Object.keys(user.items.gear.owned).length).to.equal(initialGear + 1);
|
||||
expect(get(user, path)).to.be.true;
|
||||
expect(user.balance).to.equal(usersStartingGems - 0.5);
|
||||
});
|
||||
|
||||
it('unlocks an item (background)', () => {
|
||||
const initialBackgrounds = Object.keys(user.purchased.background).length;
|
||||
const [, message] = unlock(user, { query: { path: backgroundUnlockPath } });
|
||||
|
||||
expect(message).to.equal(i18n.t('unlocked'));
|
||||
expect(user.purchased.background.giant_florals).to.be.true;
|
||||
});
|
||||
|
||||
it('reduces a user\'s balance', () => {
|
||||
const [, message] = unlock(user, { query: { path: unlockPath } });
|
||||
|
||||
expect(message).to.equal(i18n.t('unlocked'));
|
||||
expect(user.balance).to.equal(usersStartingGems - unlockCost);
|
||||
expect(Object.keys(user.purchased.background).length).to.equal(initialBackgrounds + 1);
|
||||
expect(get(user.purchased, backgroundUnlockPath)).to.be.true;
|
||||
expect(user.balance).to.equal(usersStartingGems - 1.75);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,31 +5,31 @@
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"test:unit": "vue-cli-service test:unit --require ./tests/unit/helpers.js",
|
||||
"lint": "vue-cli-service lint .",
|
||||
"lint-no-fix": "vue-cli-service lint --no-fix .",
|
||||
"postinstall": "node ./scripts/npm-postinstall.js",
|
||||
"storybook:build": "vue-cli-service storybook:build -c config/storybook -o dist/storybook",
|
||||
"storybook:serve": "vue-cli-service storybook:serve -p 6006 -c config/storybook",
|
||||
"test:unit": "vue-cli-service test:unit --require ./tests/unit/helpers.js"
|
||||
"storybook:serve": "vue-cli-service storybook:serve -p 6006 -c config/storybook"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.3.1",
|
||||
"@vue/cli-plugin-eslint": "^4.3.1",
|
||||
"@vue/cli-plugin-router": "^4.3.1",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.3.1",
|
||||
"@vue/cli-service": "^4.3.1",
|
||||
"@storybook/addon-actions": "^5.3.18",
|
||||
"@storybook/addon-knobs": "^5.3.18",
|
||||
"@storybook/addon-links": "^5.3.18",
|
||||
"@storybook/addon-notes": "^5.3.18",
|
||||
"@storybook/vue": "^5.3.18",
|
||||
"@storybook/addon-actions": "^5.3.19",
|
||||
"@storybook/addon-knobs": "^5.3.19",
|
||||
"@storybook/addon-links": "^5.3.19",
|
||||
"@storybook/addon-notes": "^5.3.19",
|
||||
"@storybook/vue": "^5.3.19",
|
||||
"@vue/cli-plugin-babel": "^4.4.1",
|
||||
"@vue/cli-plugin-eslint": "^4.4.1",
|
||||
"@vue/cli-plugin-router": "^4.4.1",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.4.1",
|
||||
"@vue/cli-service": "^4.4.1",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"amplitude-js": "^5.11.0",
|
||||
"axios": "^0.19.2",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"bootstrap": "^4.4.1",
|
||||
"bootstrap-vue": "^2.13.1",
|
||||
"bootstrap": "^4.5.0",
|
||||
"bootstrap-vue": "^2.15.0",
|
||||
"chai": "^4.1.2",
|
||||
"core-js": "^3.6.5",
|
||||
"eslint": "^6.8.0",
|
||||
@@ -38,13 +38,13 @@
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"habitica-markdown": "^2.0.0",
|
||||
"hellojs": "^1.18.4",
|
||||
"inspectpack": "^4.4.0",
|
||||
"inspectpack": "^4.5.2",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": "^3.5.1",
|
||||
"lodash": "^4.17.15",
|
||||
"moment": "^2.25.3",
|
||||
"moment": "^2.26.0",
|
||||
"nconf": "^0.10.0",
|
||||
"sass": "^1.26.5",
|
||||
"sass": "^1.26.8",
|
||||
"sass-loader": "^8.0.2",
|
||||
"smartbanner.js": "^1.15.0",
|
||||
"svg-inline-loader": "^0.8.2",
|
||||
@@ -56,9 +56,9 @@
|
||||
"vue": "^2.6.11",
|
||||
"vue-cli-plugin-storybook": "^0.6.1",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
"vue-router": "^3.1.6",
|
||||
"vue-router": "^3.3.2",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuedraggable": "^2.23.1",
|
||||
"vuedraggable": "^2.23.2",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||
"webpack": "^4.43.0"
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isUserLoggedIn', 'browserTimezoneOffset', 'isUserLoaded']),
|
||||
...mapState(['isUserLoggedIn', 'browserTimezoneOffset', 'isUserLoaded', 'notificationsRemoved']),
|
||||
...mapState({ user: 'user.data' }),
|
||||
isStaticPage () {
|
||||
return this.$route.meta.requiresLogin === false;
|
||||
@@ -361,13 +361,55 @@ export default {
|
||||
showSpinner: false,
|
||||
});
|
||||
|
||||
// Set up Error interceptors
|
||||
axios.interceptors.response.use(response => {
|
||||
if (this.user && response.data && response.data.notifications) {
|
||||
this.$set(this.user, 'notifications', response.data.notifications);
|
||||
axios.interceptors.response.use(response => { // Set up Response interceptors
|
||||
// Verify that the user was not updated from another browser/app/client
|
||||
// If it was, sync
|
||||
const { url } = response.config;
|
||||
const { method } = response.config;
|
||||
|
||||
const isApiCall = url.indexOf('api/v4') !== -1;
|
||||
const userV = response.data && response.data.userV;
|
||||
const isCron = url.indexOf('/api/v4/cron') === 0 && method === 'post';
|
||||
|
||||
if (this.isUserLoaded && isApiCall && userV) {
|
||||
const oldUserV = this.user._v;
|
||||
this.user._v = userV;
|
||||
|
||||
// Do not sync again if already syncing
|
||||
const isUserSync = url.indexOf('/api/v4/user') === 0 && method === 'get';
|
||||
const isTasksSync = url.indexOf('/api/v4/tasks/user') === 0 && method === 'get';
|
||||
// exclude chat seen requests because with real time chat they would be too many
|
||||
const isChatSeen = url.indexOf('/chat/seen') !== -1 && method === 'post';
|
||||
// exclude POST /api/v4/cron because the user is synced automatically after cron runs
|
||||
|
||||
// Something has changed on the user object that was not tracked here, sync the user
|
||||
if (userV - oldUserV > 1 && !isCron && !isChatSeen && !isUserSync && !isTasksSync) {
|
||||
Promise.all([
|
||||
this.$store.dispatch('user:fetch', { forceLoad: true }),
|
||||
this.$store.dispatch('tasks:fetchUserTasks', { forceLoad: true }),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Store the app version from the server
|
||||
const serverAppVersion = response.data && response.data.appVersion;
|
||||
|
||||
if (serverAppVersion && this.$store.state.serverAppVersion !== response.data.appVersion) {
|
||||
this.$store.state.serverAppVersion = serverAppVersion;
|
||||
}
|
||||
|
||||
// Store the notifications, filtering those that have already been read
|
||||
// See store/index.js on why this is necessary
|
||||
if (this.user && response.data && response.data.notifications) {
|
||||
const filteredNotifications = response.data.notifications.filter(serverNotification => {
|
||||
if (this.notificationsRemoved.includes(serverNotification.id)) return false;
|
||||
return true;
|
||||
});
|
||||
this.$set(this.user, 'notifications', filteredNotifications);
|
||||
}
|
||||
|
||||
return response;
|
||||
}, error => {
|
||||
}, error => { // Set up Error interceptors
|
||||
if (error.response.status >= 400) {
|
||||
const isBanned = this.checkForBannedUser(error);
|
||||
if (isBanned === true) return null; // eslint-disable-line consistent-return
|
||||
@@ -425,51 +467,6 @@ export default {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
axios.interceptors.response.use(response => {
|
||||
// Verify that the user was not updated from another browser/app/client
|
||||
// If it was, sync
|
||||
const { url } = response.config;
|
||||
const { method } = response.config;
|
||||
|
||||
const isApiCall = url.indexOf('api/v4') !== -1;
|
||||
const userV = response.data && response.data.userV;
|
||||
const isCron = url.indexOf('/api/v4/cron') === 0 && method === 'post';
|
||||
|
||||
if (this.isUserLoaded && isApiCall && userV) {
|
||||
const oldUserV = this.user._v;
|
||||
this.user._v = userV;
|
||||
|
||||
// Do not sync again if already syncing
|
||||
const isUserSync = url.indexOf('/api/v4/user') === 0 && method === 'get';
|
||||
const isTasksSync = url.indexOf('/api/v4/tasks/user') === 0 && method === 'get';
|
||||
// exclude chat seen requests because with real time chat they would be too many
|
||||
const isChatSeen = url.indexOf('/chat/seen') !== -1 && method === 'post';
|
||||
// exclude POST /api/v4/cron because the user is synced automatically after cron runs
|
||||
|
||||
// Something has changed on the user object that was not tracked here, sync the user
|
||||
if (userV - oldUserV > 1 && !isCron && !isChatSeen && !isUserSync && !isTasksSync) {
|
||||
Promise.all([
|
||||
this.$store.dispatch('user:fetch', { forceLoad: true }),
|
||||
this.$store.dispatch('tasks:fetchUserTasks', { forceLoad: true }),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the client is updated
|
||||
// const serverAppVersion = response.data.appVersion;
|
||||
// let serverAppVersionState = this.$store.state.serverAppVersion;
|
||||
// if (isApiCall && !serverAppVersionState) {
|
||||
// this.$store.state.serverAppVersion = serverAppVersion;
|
||||
// } else if (isApiCall && serverAppVersionState !== serverAppVersion) {
|
||||
// if (document.activeElement.tagName !== 'INPUT'
|
||||
// || confirm(this.$t('habiticaHasUpdated'))) {
|
||||
// location.reload(true);
|
||||
// }
|
||||
// }
|
||||
|
||||
return response;
|
||||
});
|
||||
|
||||
// Setup listener for title
|
||||
this.$store.watch(state => state.title, title => {
|
||||
document.title = title;
|
||||
|
||||
@@ -1,42 +1,24 @@
|
||||
.promo_armoire_backgrounds_202005 {
|
||||
.promo_armoire_backgrounds_202006 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -391px 0px;
|
||||
background-position: -259px 0px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_fairy_sunshine_potions {
|
||||
.promo_mystery_202006 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -391px -148px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_jungle_buddies_bundle {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -434px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_202005 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -434px;
|
||||
background-position: 0px -259px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -391px -296px;
|
||||
background-position: -259px -148px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.scene_casting_spells {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -211px;
|
||||
width: 312px;
|
||||
height: 222px;
|
||||
}
|
||||
.scene_pets_resting {
|
||||
.scene_hiking {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 390px;
|
||||
height: 210px;
|
||||
width: 258px;
|
||||
height: 258px;
|
||||
}
|
||||
|
||||
@@ -1,102 +1,120 @@
|
||||
.quest_TEMPLATE_FOR_MISSING_IMAGE {
|
||||
.quest_bunny {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -502px -1546px;
|
||||
width: 221px;
|
||||
height: 39px;
|
||||
background-position: 0px -1546px;
|
||||
width: 210px;
|
||||
height: 186px;
|
||||
}
|
||||
.quest_dilatory {
|
||||
.quest_butterfly {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -660px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_cheetah {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dilatoryDistress1 {
|
||||
.quest_cow {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -1332px;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
background-position: -1760px 0px;
|
||||
width: 174px;
|
||||
height: 213px;
|
||||
}
|
||||
.quest_dilatoryDistress2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1762px -567px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_dilatoryDistress3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dilatory_derby {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dolphin {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_dustbunnies {
|
||||
.quest_dilatory {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_egg {
|
||||
.quest_dilatoryDistress1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1762px -359px;
|
||||
width: 165px;
|
||||
height: 207px;
|
||||
background-position: -222px -1332px;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
}
|
||||
.quest_evilsanta {
|
||||
.quest_dilatoryDistress2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1762px -1171px;
|
||||
width: 118px;
|
||||
height: 131px;
|
||||
background-position: -1760px -422px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_evilsanta2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_falcon {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_ferret {
|
||||
.quest_dilatoryDistress3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_frog {
|
||||
.quest_dilatory_derby {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px 0px;
|
||||
width: 221px;
|
||||
height: 213px;
|
||||
background-position: -220px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_ghost_stag {
|
||||
.quest_dolphin {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_goldenknight1 {
|
||||
.quest_dustbunnies {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_egg {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1760px -214px;
|
||||
width: 165px;
|
||||
height: 207px;
|
||||
}
|
||||
.quest_evilsanta {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1760px -875px;
|
||||
width: 118px;
|
||||
height: 131px;
|
||||
}
|
||||
.quest_evilsanta2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_falcon {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_ferret {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_frog {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -1332px;
|
||||
width: 221px;
|
||||
height: 213px;
|
||||
}
|
||||
.quest_ghost_stag {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_goldenknight1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_goldenknight2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -1546px;
|
||||
background-position: -462px -1546px;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
}
|
||||
@@ -108,295 +126,271 @@
|
||||
}
|
||||
.quest_gryphon {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -874px -1332px;
|
||||
background-position: -876px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_guineapig {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -452px;
|
||||
background-position: -1100px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_harpy {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px 0px;
|
||||
background-position: -1100px -660px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_hedgehog {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -431px -1332px;
|
||||
background-position: -433px -1332px;
|
||||
width: 219px;
|
||||
height: 186px;
|
||||
}
|
||||
.quest_hippo {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -220px;
|
||||
background-position: -220px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_horse {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -440px;
|
||||
background-position: -440px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_kangaroo {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -672px;
|
||||
background-position: -660px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_kraken {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1091px -1332px;
|
||||
background-position: -1093px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_lostMasterclasser1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -672px;
|
||||
background-position: -1100px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_lostMasterclasser2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -672px;
|
||||
background-position: -1320px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_lostMasterclasser3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -672px;
|
||||
background-position: -1320px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_mayhemMistiflying1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1762px -718px;
|
||||
background-position: -1760px -724px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_mayhemMistiflying2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -672px;
|
||||
background-position: -220px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_mayhemMistiflying3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px 0px;
|
||||
background-position: -1320px -880px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_monkey {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -220px;
|
||||
background-position: 0px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moon1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px -214px;
|
||||
background-position: -1540px -437px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_moon2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -440px;
|
||||
background-position: -440px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moon3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -660px;
|
||||
background-position: -660px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -892px;
|
||||
background-position: -880px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -892px;
|
||||
background-position: -1100px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_moonstone3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -892px;
|
||||
background-position: -1320px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_nudibranch {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px -431px;
|
||||
background-position: -1540px -871px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_octopus {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -651px -1332px;
|
||||
background-position: -653px -1332px;
|
||||
width: 222px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_owl {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -892px;
|
||||
background-position: -1540px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_peacock {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px -648px;
|
||||
background-position: -1540px -220px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_penguin {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1762px -175px;
|
||||
background-position: 0px -1733px;
|
||||
width: 190px;
|
||||
height: 183px;
|
||||
}
|
||||
.quest_pterodactyl {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -892px;
|
||||
background-position: -220px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_rat {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -892px;
|
||||
background-position: -1320px -440px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_robot {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px 0px;
|
||||
background-position: -880px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_rock {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px -865px;
|
||||
background-position: -1540px -654px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_rooster {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1762px 0px;
|
||||
background-position: -1527px -1332px;
|
||||
width: 213px;
|
||||
height: 174px;
|
||||
}
|
||||
.quest_ruby {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -220px;
|
||||
background-position: 0px -892px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_sabretooth {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -440px;
|
||||
background-position: -1100px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_seaserpent {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -660px;
|
||||
background-position: -880px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_sheep {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -880px;
|
||||
background-position: -220px -672px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_silver {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -1112px;
|
||||
background-position: -880px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_slime {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -220px -1112px;
|
||||
background-position: -660px -452px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_sloth {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -440px -1112px;
|
||||
background-position: -660px -220px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_snail {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: 0px -1332px;
|
||||
background-position: -1540px -1088px;
|
||||
width: 219px;
|
||||
height: 213px;
|
||||
}
|
||||
.quest_snake {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1308px -1332px;
|
||||
background-position: -1310px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
.quest_spider {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -251px -1546px;
|
||||
background-position: -211px -1546px;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_squirrel {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -660px -1112px;
|
||||
background-position: -660px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_stoikalmCalamity1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1762px -869px;
|
||||
background-position: -1760px -573px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_stoikalmCalamity2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -880px -1112px;
|
||||
background-position: 0px -232px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_stoikalmCalamity3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1100px -1112px;
|
||||
background-position: -1100px 0px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_taskwoodsTerror1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1762px -1020px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_taskwoodsTerror2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1540px -1082px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
.quest_taskwoodsTerror3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1320px -1112px;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_treeling {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-13.png');
|
||||
background-position: -1525px -1332px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 470 KiB After Width: | Height: | Size: 470 KiB |
|
Before Width: | Height: | Size: 615 KiB After Width: | Height: | Size: 603 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 431 KiB After Width: | Height: | Size: 425 KiB |
|
Before Width: | Height: | Size: 223 KiB After Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 213 KiB After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 151 KiB |
@@ -41,4 +41,9 @@
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding: 0 16px;
|
||||
border-left: 4px solid #e1e0e3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="row">
|
||||
<div
|
||||
v-for="heroClass in classes"
|
||||
:key="heroClass"
|
||||
:key="`${heroClass}-avatar`"
|
||||
class="col-md-3"
|
||||
>
|
||||
<div @click="selectedClass = heroClass">
|
||||
@@ -60,7 +60,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-for="heroClass in classes"
|
||||
:key="heroClass"
|
||||
:key="`${heroClass}-explanation`"
|
||||
>
|
||||
<div
|
||||
v-if="selectedClass === heroClass"
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
class="avatar"
|
||||
:member="user"
|
||||
:avatar-only="true"
|
||||
:hide-class-badge="true"
|
||||
:with-background="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -64,7 +64,7 @@ export default {
|
||||
this.html = response.data.html;
|
||||
});
|
||||
},
|
||||
destroyed () {
|
||||
beforeDestroy () {
|
||||
this.$root.$off('bv::show::modal');
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://trello.com/c/odmhIqyW/440-read-first-table-of-contents"
|
||||
href="https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link"
|
||||
target="_blank"
|
||||
>{{ $t('requestFeature') }}</a>
|
||||
</li>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</div>
|
||||
<div class="col-12 col-md-6 text-right">
|
||||
<div
|
||||
class="box"
|
||||
class="box member-count"
|
||||
@click="showMemberModal()"
|
||||
>
|
||||
<div
|
||||
@@ -93,6 +93,7 @@
|
||||
:members="members"
|
||||
:challenge-id="challengeId"
|
||||
@member-selected="openMemberProgressModal"
|
||||
@opened="initialMembersLoad()"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 text-right">
|
||||
@@ -279,6 +280,10 @@
|
||||
font-size: 20px;
|
||||
vertical-align: bottom;
|
||||
|
||||
&.member-count:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
@@ -364,6 +369,7 @@ export default {
|
||||
}),
|
||||
challenge: {},
|
||||
members: [],
|
||||
membersLoaded: false,
|
||||
tasksByType: {
|
||||
habit: [],
|
||||
daily: [],
|
||||
@@ -427,8 +433,6 @@ export default {
|
||||
this.$router.push('/challenges/findChallenges');
|
||||
return;
|
||||
}
|
||||
this.members = await this
|
||||
.loadMembers({ challengeId: this.searchId, includeAllPublicFields: true });
|
||||
const tasks = await this.$store.dispatch('tasks:getChallengeTasks', { challengeId: this.searchId });
|
||||
this.tasksByType = {
|
||||
habit: [],
|
||||
@@ -454,7 +458,22 @@ export default {
|
||||
}
|
||||
return this.$store.dispatch('members:getChallengeMembers', payload);
|
||||
},
|
||||
initialMembersLoad () {
|
||||
this.$store.state.memberModalOptions.loading = true;
|
||||
if (!this.membersLoaded) {
|
||||
this.membersLoaded = true;
|
||||
|
||||
this.loadMembers({
|
||||
challengeId: this.searchId,
|
||||
includeAllPublicFields: true,
|
||||
}).then(m => {
|
||||
this.members.push(...m);
|
||||
this.$store.state.memberModalOptions.loading = false;
|
||||
});
|
||||
} else {
|
||||
this.$store.state.memberModalOptions.loading = false;
|
||||
}
|
||||
},
|
||||
editTask (task) {
|
||||
this.taskFormPurpose = 'edit';
|
||||
this.editingTask = cloneDeep(task);
|
||||
@@ -489,6 +508,8 @@ export default {
|
||||
this.tasksByType[task.type].splice(index, 1);
|
||||
},
|
||||
showMemberModal () {
|
||||
this.initialMembersLoad();
|
||||
|
||||
this.$root.$emit('habitica:show-member-modal', {
|
||||
challengeId: this.challenge._id,
|
||||
groupId: 'challenge', // @TODO: change these terrible settings
|
||||
@@ -501,8 +522,8 @@ export default {
|
||||
async joinChallenge () {
|
||||
this.user.challenges.push(this.searchId);
|
||||
this.challenge = await this.$store.dispatch('challenges:joinChallenge', { challengeId: this.searchId });
|
||||
this.members = await this
|
||||
.loadMembers({ challengeId: this.searchId, includeAllPublicFields: true });
|
||||
this.membersLoaded = false;
|
||||
this.members = [];
|
||||
|
||||
await this.$store.dispatch('tasks:fetchUserTasks', { forceLoad: true });
|
||||
},
|
||||
@@ -511,10 +532,11 @@ export default {
|
||||
},
|
||||
async updateChallenge () {
|
||||
this.challenge = await this.$store.dispatch('challenges:getChallenge', { challengeId: this.searchId });
|
||||
this.members = await this
|
||||
.loadMembers({ challengeId: this.searchId, includeAllPublicFields: true });
|
||||
this.membersLoaded = false;
|
||||
this.members = [];
|
||||
},
|
||||
closeChallenge () {
|
||||
this.initialMembersLoad();
|
||||
this.$root.$emit('bv::show::modal', 'close-challenge-modal');
|
||||
},
|
||||
edit () {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</p>
|
||||
<div
|
||||
ref="markdownContainer"
|
||||
class="text"
|
||||
class="text markdown"
|
||||
v-html="parseMarkdown(msg.text)"
|
||||
></div>
|
||||
<hr>
|
||||
@@ -166,7 +166,6 @@
|
||||
color: #4e4a57;
|
||||
text-align: left !important;
|
||||
min-height: 0rem;
|
||||
margin-bottom: -0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ export default {
|
||||
created () {
|
||||
window.addEventListener('scroll', this.handleScroll);
|
||||
},
|
||||
destroyed () {
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('scroll', this.handleScroll);
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -81,7 +81,7 @@ export default {
|
||||
this.$root.$emit('bv::show::modal', 'copyAsTodo');
|
||||
});
|
||||
},
|
||||
destroyed () {
|
||||
beforeDestroy () {
|
||||
this.$root.$off('habitica::copy-as-todo');
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -122,10 +122,10 @@ export default {
|
||||
};
|
||||
},
|
||||
},
|
||||
created () {
|
||||
mounted () {
|
||||
this.$root.$on('habitica::report-chat', this.handleReport);
|
||||
},
|
||||
destroyed () {
|
||||
beforeDestroy () {
|
||||
this.$root.$off('habitica::report-chat', this.handleReport);
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -385,7 +385,7 @@
|
||||
import extend from 'lodash/extend';
|
||||
import groupUtilities from '@/mixins/groupsUtilities';
|
||||
import styleHelper from '@/mixins/styleHelper';
|
||||
import { mapState } from '@/libs/store';
|
||||
import { mapState, mapGetters } from '@/libs/store';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import startQuestModal from './startQuestModal';
|
||||
import questDetailsModal from './questDetailsModal';
|
||||
@@ -447,6 +447,7 @@ export default {
|
||||
bronzeGuildBadgeIcon,
|
||||
}),
|
||||
members: [],
|
||||
membersLoaded: false,
|
||||
selectedQuest: {},
|
||||
chat: {
|
||||
submitDisable: false,
|
||||
@@ -455,7 +456,12 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
...mapGetters({
|
||||
partyMembers: 'party:members',
|
||||
}),
|
||||
partyStore () {
|
||||
return this.$store.state.party;
|
||||
},
|
||||
@@ -487,10 +493,15 @@ export default {
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
async mounted () {
|
||||
if (this.isParty) this.searchId = 'party';
|
||||
if (!this.searchId) this.searchId = this.groupId;
|
||||
this.load();
|
||||
await this.fetchGuild();
|
||||
|
||||
this.$root.$on('updatedGroup', this.onGroupUpdate);
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$root.$off('updatedGroup', this.onGroupUpdate);
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.$set(this, 'searchId', to.params.groupId);
|
||||
@@ -501,19 +512,9 @@ export default {
|
||||
acceptCommunityGuidelines () {
|
||||
this.$store.dispatch('user:set', { 'flags.communityGuidelinesAccepted': true });
|
||||
},
|
||||
async load () {
|
||||
if (this.isParty) {
|
||||
this.searchId = 'party';
|
||||
// @TODO: Set up from old client. Decide what we need and what we don't
|
||||
// Check Desktop notifs
|
||||
// Load invites
|
||||
}
|
||||
await this.fetchGuild();
|
||||
|
||||
this.$root.$on('updatedGroup', group => {
|
||||
const updatedGroup = extend(this.group, group);
|
||||
this.$set(this.group, updatedGroup);
|
||||
});
|
||||
onGroupUpdate (group) {
|
||||
const updatedGroup = extend(this.group, group);
|
||||
this.$set(this.group, updatedGroup);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -531,6 +532,26 @@ export default {
|
||||
return this.$store.dispatch('members:getGroupMembers', payload);
|
||||
},
|
||||
showMemberModal () {
|
||||
this.$store.state.memberModalOptions.loading = true;
|
||||
|
||||
if (this.isParty) {
|
||||
this.membersLoaded = true;
|
||||
this.members = this.partyMembers;
|
||||
this.$store.state.memberModalOptions.loading = false;
|
||||
} else if (!this.membersLoaded) {
|
||||
this.membersLoaded = true;
|
||||
|
||||
this.loadMembers({
|
||||
groupId: this.group._id,
|
||||
includeAllPublicFields: true,
|
||||
}).then(m => {
|
||||
this.members.push(...m);
|
||||
this.$store.state.memberModalOptions.loading = false;
|
||||
});
|
||||
} else {
|
||||
this.$store.state.memberModalOptions.loading = false;
|
||||
}
|
||||
|
||||
this.$root.$emit('habitica:show-member-modal', {
|
||||
groupId: this.group._id,
|
||||
group: this.group,
|
||||
@@ -565,19 +586,13 @@ export default {
|
||||
|
||||
const groupId = this.searchId === 'party' ? this.user.party._id : this.searchId;
|
||||
if (this.hasUnreadMessages(groupId)) {
|
||||
// Delay by 1sec to make sure it returns after
|
||||
// other requests that don't have the notification marked as read
|
||||
setTimeout(() => {
|
||||
this.$store.dispatch('chat:markChatSeen', { groupId });
|
||||
this.$delete(this.user.newMessages, groupId);
|
||||
}, 1000);
|
||||
const notification = this.user
|
||||
.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupId);
|
||||
const notificationId = notification && notification.id;
|
||||
this.$store.dispatch('chat:markChatSeen', { groupId, notificationId });
|
||||
}
|
||||
|
||||
this.members = await this.loadMembers({
|
||||
groupId: this.group._id,
|
||||
includeAllPublicFields: true,
|
||||
});
|
||||
},
|
||||
// returns the notification id or false
|
||||
hasUnreadMessages (groupId) {
|
||||
if (this.user.newMessages[groupId]) return true;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<b-form-input
|
||||
v-model="workingGroup.name"
|
||||
type="text"
|
||||
:placeholder="$t('newGuildPlaceholder')"
|
||||
:placeholder="isParty ? $t('newPartyPlaceholder') : $t('newGuildPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
@@ -99,14 +99,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedPage === 'members'">
|
||||
<loading-gryphon v-if="loading" />
|
||||
<div
|
||||
v-if="selectedPage === 'members' && !loading"
|
||||
:class="{'mt-1': invites.length === 0}"
|
||||
>
|
||||
<div
|
||||
v-for="(member, index) in sortedMembers"
|
||||
:key="member._id"
|
||||
class="row"
|
||||
>
|
||||
<div class="col-11 no-padding-left">
|
||||
<member-details :member="member" />
|
||||
<member-details
|
||||
:member="member"
|
||||
:class-badge-position="'next-to-name'"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-1 actions">
|
||||
<b-dropdown right="right">
|
||||
@@ -201,7 +208,7 @@
|
||||
class="row gradient"
|
||||
></div>
|
||||
</div>
|
||||
<div v-if="selectedPage === 'invites'">
|
||||
<div v-if="selectedPage === 'invites' && !loading">
|
||||
<div
|
||||
v-for="(member, index) in invites"
|
||||
:key="member._id"
|
||||
@@ -234,14 +241,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('close') }}
|
||||
</button>
|
||||
</div>
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -254,11 +253,6 @@
|
||||
box-shadow: 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
background-color: #edecee;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.small-text, .character-name {
|
||||
color: #878190;
|
||||
}
|
||||
@@ -270,6 +264,8 @@
|
||||
.modal-body {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.member-details {
|
||||
@@ -378,6 +374,7 @@ import isEmpty from 'lodash/isEmpty';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import removeMemberModal from '@/components/members/removeMemberModal';
|
||||
import loadingGryphon from '@/components/ui/loadingGryphon';
|
||||
import MemberDetails from '../memberDetails';
|
||||
import removeIcon from '@/assets/members/remove.svg';
|
||||
import messageIcon from '@/assets/members/message.svg';
|
||||
@@ -388,6 +385,7 @@ export default {
|
||||
components: {
|
||||
MemberDetails,
|
||||
removeMemberModal,
|
||||
loadingGryphon,
|
||||
},
|
||||
props: ['hideBadge'],
|
||||
data () {
|
||||
@@ -474,6 +472,9 @@ export default {
|
||||
challengeId () {
|
||||
return this.$store.state.memberModalOptions.challengeId;
|
||||
},
|
||||
loading () {
|
||||
return this.$store.state.memberModalOptions.loading;
|
||||
},
|
||||
sortedMembers () {
|
||||
let sortedMembers = this.members.slice(); // shallow clone to avoid infinite loop
|
||||
|
||||
@@ -504,16 +505,6 @@ export default {
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
groupId () {
|
||||
// @TODO: We might not need this since groupId is computed now
|
||||
this.getMembers();
|
||||
},
|
||||
challengeId () {
|
||||
this.getMembers();
|
||||
},
|
||||
group () {
|
||||
this.getMembers();
|
||||
},
|
||||
// Watches `searchTerm` and if present, performs a `searchMembers` action
|
||||
// and usual `getMembers` otherwise
|
||||
searchTerm () {
|
||||
@@ -537,7 +528,7 @@ export default {
|
||||
this.getMembers();
|
||||
});
|
||||
},
|
||||
destroyed () {
|
||||
beforeDestroy () {
|
||||
this.$root.$off('habitica:show-member-modal');
|
||||
},
|
||||
methods: {
|
||||
@@ -558,8 +549,9 @@ export default {
|
||||
});
|
||||
},
|
||||
async getMembers () {
|
||||
const { groupId } = this;
|
||||
this.members = this.$store.state.memberModalOptions.viewingMembers;
|
||||
|
||||
const { groupId } = this;
|
||||
if (groupId && groupId !== 'challenge') {
|
||||
const invites = await this.$store.dispatch('members:getGroupInvites', {
|
||||
groupId,
|
||||
@@ -567,8 +559,6 @@ export default {
|
||||
});
|
||||
this.invites = invites;
|
||||
}
|
||||
|
||||
this.members = this.$store.state.memberModalOptions.viewingMembers;
|
||||
},
|
||||
async clickMember (uid, forceShow) {
|
||||
const user = this.$store.state.user.data;
|
||||
|
||||
@@ -209,7 +209,7 @@ export default {
|
||||
|
||||
this.$root.$on('selectQuest', this.selectQuest);
|
||||
},
|
||||
destroyed () {
|
||||
beforeDestroy () {
|
||||
this.$root.$off('selectQuest', this.selectQuest);
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
<li>
|
||||
<a
|
||||
v-once
|
||||
href="https://trello.com/c/odmhIqyW/440-read-first-table-of-contents"
|
||||
href="https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link"
|
||||
target="_blank"
|
||||
>{{ $t('requestFeature') }}</a>
|
||||
</li>
|
||||
|
||||
@@ -61,13 +61,9 @@
|
||||
<small>
|
||||
1-7 for normal contributors, 8 for moderators, 9 for staff. This determines which items, pets, and mounts are available, and name-tag coloring. Tiers 8 and 9 are automatically given admin status. <!-- eslint-disable-line max-len -->
|
||||
<a
|
||||
href="https://habitica.fandom.com/wiki/Contributor_Rewards"
|
||||
target="_blank"
|
||||
href="https://trello.com/c/wkFzONhE/277-contributor-gear"
|
||||
>More details (1-7)</a>,
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://github.com/HabitRPG/habitica/issues/3801"
|
||||
>more details (8-9)</a>
|
||||
>More details</a>
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
@@ -222,8 +222,8 @@ export default {
|
||||
this.$root.$emit('bv::show::modal', 'invite-modal');
|
||||
});
|
||||
},
|
||||
destroyed () {
|
||||
this.$root.off('inviteModal::inviteToGroup');
|
||||
beforeDestroy () {
|
||||
this.$root.$off('inviteModal::inviteToGroup');
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
@@ -250,6 +250,7 @@ export default {
|
||||
groupId: party.data._id,
|
||||
viewingMembers: this.partyMembers,
|
||||
group: party.data,
|
||||
fetchMoreMembers: p => this.$store.dispatch('members:getGroupMembers', p),
|
||||
});
|
||||
},
|
||||
setPartyMembersWidth ($event) {
|
||||
|
||||
@@ -323,7 +323,7 @@
|
||||
</router-link>
|
||||
<a
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
href="https://trello.com/c/odmhIqyW/440-read-first-table-of-contents"
|
||||
href="https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link"
|
||||
target="_blank"
|
||||
>{{ $t('requestAF') }}</a>
|
||||
<a
|
||||
|
||||
@@ -182,10 +182,10 @@ export default {
|
||||
remove () {
|
||||
if (this.notification.type === 'NEW_CHAT_MESSAGE') {
|
||||
const groupId = this.notification.data.group.id;
|
||||
this.$store.dispatch('chat:markChatSeen', { groupId });
|
||||
if (this.user.newMessages[groupId]) {
|
||||
this.$delete(this.user.newMessages, groupId);
|
||||
}
|
||||
this.$store.dispatch('chat:markChatSeen', {
|
||||
groupId,
|
||||
notificationId: this.notification.id,
|
||||
});
|
||||
} else {
|
||||
this.readNotification({ notificationId: this.notification.id });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<base-notification
|
||||
v-if="worldBoss.active"
|
||||
v-if="worldBoss && worldBoss.active"
|
||||
:can-remove="false"
|
||||
:notification="{}"
|
||||
:read-after-click="false"
|
||||
@@ -11,10 +11,16 @@
|
||||
class="background"
|
||||
>
|
||||
<div class="text">
|
||||
<div class="title">
|
||||
<div
|
||||
v-once
|
||||
class="title"
|
||||
>
|
||||
{{ $t('worldBoss') }}
|
||||
</div>
|
||||
<div class="sub-title">
|
||||
<div
|
||||
v-once
|
||||
class="sub-title"
|
||||
>
|
||||
{{ $t('questDysheartenerText') }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,6 +46,7 @@
|
||||
</div>
|
||||
<div class="pending-damage">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon"
|
||||
v-html="icons.sword"
|
||||
></div>
|
||||
@@ -182,11 +189,13 @@ export default {
|
||||
sword,
|
||||
}),
|
||||
questData,
|
||||
worldBoss: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
worldBoss: 'worldState.data.worldBoss',
|
||||
}),
|
||||
bossHp () {
|
||||
if (this.worldBoss && this.worldBoss.progress) {
|
||||
return this.worldBoss.progress.hp;
|
||||
@@ -195,8 +204,7 @@ export default {
|
||||
},
|
||||
},
|
||||
async mounted () {
|
||||
const result = await this.$store.dispatch('worldState:getWorldState');
|
||||
this.worldBoss = result.worldBoss;
|
||||
await this.$store.dispatch('worldState:getWorldState');
|
||||
},
|
||||
methods: {
|
||||
action () {
|
||||
|
||||
@@ -419,6 +419,11 @@ export default {
|
||||
cardType: '',
|
||||
messageOptions: 0,
|
||||
},
|
||||
quantitySnapshot: {
|
||||
eggs: null,
|
||||
hatchingPotions: null,
|
||||
food: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -443,7 +448,9 @@ export default {
|
||||
if (itemQuantity > 0 && isAllowed) {
|
||||
const item = contentItems[itemKey];
|
||||
|
||||
const isSearched = !searchText || item.text().toLowerCase().indexOf(searchText) !== -1;
|
||||
const isSearched = !searchText || item.text()
|
||||
.toLowerCase()
|
||||
.indexOf(searchText) !== -1;
|
||||
if (isSearched) {
|
||||
itemsArray.push({
|
||||
...item,
|
||||
@@ -458,12 +465,16 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
itemsArray.sort((a, b) => {
|
||||
if (this.sortBy === 'quantity') {
|
||||
return b.quantity - a.quantity;
|
||||
} // AZ
|
||||
return a.text.localeCompare(b.text);
|
||||
});
|
||||
if (this.sortBy === 'quantity') {
|
||||
// Store original quantities, to avoid reordering when using items.
|
||||
const quantitySnapshot = this.quantitySnapshot[groupKey] || Object.fromEntries(
|
||||
itemsArray.map(item => [item.key, item.quantity]),
|
||||
);
|
||||
itemsArray.sort((a, b) => quantitySnapshot[b.key] - quantitySnapshot[a.key]);
|
||||
this.quantitySnapshot[groupKey] = quantitySnapshot;
|
||||
} else {
|
||||
itemsArray.sort((a, b) => a.text.localeCompare(b.text));
|
||||
}
|
||||
});
|
||||
|
||||
const specialArray = itemsByType.special;
|
||||
|
||||
@@ -124,7 +124,7 @@ export default {
|
||||
mounted () {
|
||||
this.$root.$on('hatchedPet::open', this.openDialog);
|
||||
},
|
||||
destroyed () {
|
||||
beforeDestroy () {
|
||||
this.$root.$off('hatchedPet::open', this.openDialog);
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -154,6 +154,7 @@
|
||||
<!-- eslint-enable vue/no-use-v-if-with-v-for -->
|
||||
<div
|
||||
v-for="item in group"
|
||||
v-show="show('pet', item)"
|
||||
:key="item.key"
|
||||
v-drag.drop.food="item.key"
|
||||
class="pet-group"
|
||||
@@ -216,6 +217,7 @@
|
||||
<!-- eslint-enable vue/no-use-v-if-with-v-for -->
|
||||
<div
|
||||
v-for="item in group"
|
||||
v-show="show('mount', item)"
|
||||
:key="item.key"
|
||||
class="pet-group"
|
||||
>
|
||||
@@ -225,6 +227,7 @@
|
||||
:popover-position="'top'"
|
||||
:show-popover="true"
|
||||
@click="selectMount(item)"
|
||||
|
||||
>
|
||||
<span slot="popoverContent">
|
||||
<h4 class="popover-content-title">{{ item.name }}</h4>
|
||||
@@ -688,6 +691,11 @@ export default {
|
||||
setShowMore (key) {
|
||||
this.$_openedItemRows_toggleByType(key, !this.$_openedItemRows_isToggled(key));
|
||||
},
|
||||
show (type, item) {
|
||||
return item.canFind === undefined
|
||||
|| isOwned(type, item, this.userItems)
|
||||
|| item.canFind;
|
||||
},
|
||||
getAnimalList (animalGroup, type) {
|
||||
const { key } = animalGroup;
|
||||
|
||||
@@ -706,11 +714,14 @@ export default {
|
||||
const eggKey = specialKey.split('-')[0];
|
||||
const potionKey = specialKey.split('-')[1];
|
||||
|
||||
const { canFind, text } = this.content[`${type}Info`][specialKey];
|
||||
|
||||
animals.push({
|
||||
key: specialKey,
|
||||
eggKey,
|
||||
potionKey,
|
||||
name: this.content[`${type}Info`][specialKey].text(),
|
||||
name: text(),
|
||||
canFind,
|
||||
isOwned () {
|
||||
return isOwned(type, this, userItems);
|
||||
},
|
||||
|
||||
@@ -102,7 +102,7 @@ export default {
|
||||
mounted () {
|
||||
this.$root.$on('habitica::mount-raised', this.openDialog);
|
||||
},
|
||||
destroyed () {
|
||||
beforeDestroy () {
|
||||
this.$root.$off('habitica::mount-raised', this.openDialog);
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
class="create-dropdown"
|
||||
:text="text"
|
||||
no-flip="no-flip"
|
||||
@show="$emit('opened')"
|
||||
>
|
||||
<b-dropdown-form
|
||||
:disabled="false"
|
||||
@@ -10,10 +11,14 @@
|
||||
>
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
class="form-control"
|
||||
class="form-control member-input"
|
||||
type="text"
|
||||
>
|
||||
</b-dropdown-form>
|
||||
<loading-gryphon
|
||||
v-if="loading"
|
||||
:height="32"
|
||||
/>
|
||||
<b-dropdown-item
|
||||
v-for="member in memberResults"
|
||||
:key="member._id"
|
||||
@@ -25,13 +30,25 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.create-dropdown ::v-deep form.b-dropdown-form {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.create-dropdown ::v-deep ul.dropdown-menu {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// @TODO: how do we subclass this rather than type checking?
|
||||
import challengeMemberSearchMixin from '@/mixins/challengeMemberSearch';
|
||||
import loadingGryphon from '@/components/ui/loadingGryphon';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
loadingGryphon,
|
||||
},
|
||||
mixins: [challengeMemberSearchMixin],
|
||||
props: {
|
||||
text: {
|
||||
@@ -52,6 +69,11 @@ export default {
|
||||
memberResults: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
loading () {
|
||||
return this.$store.state.memberModalOptions.loading;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
memberResults () {
|
||||
if (this.memberResults.length > 10) this.memberResults.length = 10;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<span v-if="msg.client && user.contributor.level >= 4"> ({{ msg.client }})</span>
|
||||
</p>
|
||||
<div
|
||||
class="text"
|
||||
class="text markdown"
|
||||
v-html="parseMarkdown(msg.text)"
|
||||
></div>
|
||||
<div
|
||||
@@ -120,7 +120,6 @@
|
||||
color: $gray-50;
|
||||
text-align: left !important;
|
||||
min-height: 0rem;
|
||||
margin-bottom: -0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -226,8 +226,6 @@ export default {
|
||||
beforeDestroy () {
|
||||
this.$el.removeEventListener('selectstart', () => this.handleSelectStart());
|
||||
this.$el.removeEventListener('mouseup', () => this.handleSelectChange());
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('scroll', this.handleScroll);
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -817,10 +817,8 @@ export default {
|
||||
break;
|
||||
}
|
||||
case 'CRON':
|
||||
if (notification.data) {
|
||||
if (notification.data.hp) this.hp(notification.data.hp, 'hp');
|
||||
if (notification.data.mp && this.userHasClass) this.mp(notification.data.mp);
|
||||
}
|
||||
// Not needed because it's shown already by the userHp and userMp watchers
|
||||
// Keeping an empty block so that it gets read
|
||||
break;
|
||||
case 'SCORED_TASK':
|
||||
// Search if it is a read notification
|
||||
|
||||