Compare commits
149 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 257a52cba1 | |||
| cbac69ab11 | |||
| d2156eb97b | |||
| 7c50a66ef7 | |||
| 0be5b5c9c7 | |||
| 1b1c239aec | |||
| 237c1b0713 | |||
| 1f16891b15 | |||
| fcebd4327d | |||
| c2c5c8a86d | |||
| 8bdd56587e | |||
| 64e6e6c27a | |||
| 356302674c | |||
| ee9eb3f78a | |||
| c264465534 | |||
| 7d5dc63efc | |||
| 0d31ceea83 | |||
| da5c835ec0 | |||
| e753068aee | |||
| 9f20b5c2a8 | |||
| 18a32e8496 | |||
| f04c88c60f | |||
| 8f4e20a414 | |||
| 45ea77b743 | |||
| 83b722456c | |||
| ca7be4c0a4 | |||
| 5345b66c8d | |||
| 2188bee7cb | |||
| df43ea863c | |||
| 78d2299aaa | |||
| d67e800863 | |||
| fae18d7631 | |||
| 4ede851d30 | |||
| 6f69721d25 | |||
| 2bc4c1324c | |||
| 59bca3fd3a | |||
| d4cfa65f94 | |||
| caf47a9961 | |||
| 5097f67123 | |||
| fc8bba63df | |||
| 71f62232af | |||
| 01699229d3 | |||
| c55f611807 | |||
| a369751051 | |||
| 56ce56fc3b | |||
| a59d130c43 | |||
| 95b0a79289 | |||
| 45b41cf6b0 | |||
| ca69085e8c | |||
| b5af9b292e | |||
| 4a812a9d28 | |||
| 383bbc558b | |||
| 7effcb49e3 | |||
| 1e005a8bc4 | |||
| 04960f8ff8 | |||
| 0fd3e42fb2 | |||
| bb9582bfc5 | |||
| de4e4f3c4c | |||
| fcbc87afc2 | |||
| 6dc1011409 | |||
| e15fdbbe05 | |||
| 9510b2345c | |||
| 615cec6ae0 | |||
| 45668a0ba9 | |||
| fa9f962e45 | |||
| 4a88659364 | |||
| e4ba2bfc7e | |||
| 646ac08cb2 | |||
| 10348d3daa | |||
| 35e5a6ed35 | |||
| 8023914131 | |||
| 6ac959b9c6 | |||
| 011a462d39 | |||
| 484b9f09af | |||
| a916fb8060 | |||
| 91afb90ecc | |||
| 0bfa0cc068 | |||
| bcb10382af | |||
| 188c63cb43 | |||
| 3a172a9b8f | |||
| 376a0efa67 | |||
| fbd68c5ea3 | |||
| f6f30e8f5d | |||
| 6c8321c7ec | |||
| c7d44a0c7e | |||
| 74ae724e33 | |||
| 78306fe33d | |||
| 5c1f215eb5 | |||
| c7fd7aefc2 | |||
| fe45e62258 | |||
| c8db6a35f9 | |||
| 9fd4ffbfbf | |||
| 14c1bddca2 | |||
| e90d9d9ec4 | |||
| 2463287161 | |||
| e440a344e3 | |||
| acec30cb05 | |||
| ee51878c23 | |||
| 650c9044c8 | |||
| 1f54b59bc6 | |||
| 4035b148d2 | |||
| de5f4cb092 | |||
| 0af4561e95 | |||
| a3bbf6a149 | |||
| 24ca2cd2de | |||
| 4f167c6b2e | |||
| 11a2696179 | |||
| c934cf990d | |||
| fdfe76ca16 | |||
| 5762f85798 | |||
| d1ecb960b0 | |||
| 47fb821956 | |||
| d0eb6196d3 | |||
| bb50586622 | |||
| 8b569e2136 | |||
| da878dfa1a | |||
| 8abf0dd11d | |||
| a487c708be | |||
| 9258f8ad26 | |||
| f47ccfdb6d | |||
| 7faf443197 | |||
| 04180fe974 | |||
| e6edaca11d | |||
| 70894fa41f | |||
| 255b4061b0 | |||
| 59fb9f1c7a | |||
| 0f4a5ebe6c | |||
| a3277eeea3 | |||
| a550e32fd3 | |||
| ef4e79f520 | |||
| f51c12437d | |||
| e8edc9669a | |||
| e47845535a | |||
| 8c6938022c | |||
| 2c65b3a6a3 | |||
| 4f084d330d | |||
| 4b4cc61031 | |||
| 10bcb39b32 | |||
| 2c0da084ea | |||
| 6e183c3927 | |||
| 0f755ab80d | |||
| 8b780eaf0d | |||
| dec0488b08 | |||
| aea02d735e | |||
| 3e476c4f52 | |||
| 9cbdf0df42 | |||
| 104d888bfb | |||
| 1c719a8dbb | |||
| 00ea6a4c7f |
@@ -37,3 +37,6 @@ yarn.lock
|
||||
.elasticbeanstalk/*
|
||||
!.elasticbeanstalk/*.cfg.yml
|
||||
!.elasticbeanstalk/*.global.yml
|
||||
|
||||
# webstorm fake webpack for path intellisense
|
||||
webpack.webstorm.config
|
||||
|
||||
@@ -3,9 +3,12 @@ FROM node:12
|
||||
# Install global packages
|
||||
RUN npm install -g gulp-cli mocha
|
||||
|
||||
# Copy Habitica code into container and install dependencies
|
||||
# Copy package.json and package-lock.json into image, then install
|
||||
# dependencies.
|
||||
WORKDIR /usr/src/habitica
|
||||
COPY . /usr/src/habitica
|
||||
|
||||
COPY ["package.json", "package-lock.json", "./"]
|
||||
RUN npm install
|
||||
|
||||
# Copy the remaining source files in.
|
||||
COPY . /usr/src/habitica
|
||||
RUN npm run postinstall
|
||||
|
||||
@@ -15,8 +15,9 @@ services:
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- .:/code
|
||||
- /code/node_modules
|
||||
- .:/usr/src/habitica
|
||||
- /usr/src/habitica/node_modules
|
||||
- /usr/src/habitica/website/client/node_modules
|
||||
server:
|
||||
build:
|
||||
context: .
|
||||
@@ -32,8 +33,8 @@ services:
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- .:/code
|
||||
- /code/node_modules
|
||||
- .:/usr/src/habitica
|
||||
- /usr/src/habitica/node_modules
|
||||
mongo:
|
||||
image: mongo:3.6
|
||||
networks:
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.129.2",
|
||||
"version": "4.130.2",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.7.7",
|
||||
"@babel/preset-env": "^7.7.7",
|
||||
"@babel/register": "^7.7.7",
|
||||
"@google-cloud/trace-agent": "^4.2.4",
|
||||
"@babel/core": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.3",
|
||||
"@babel/register": "^7.8.3",
|
||||
"@google-cloud/trace-agent": "^4.2.5",
|
||||
"@slack/client": "^3.8.1",
|
||||
"accepts": "^1.3.5",
|
||||
"amazon-payments": "^0.2.7",
|
||||
"amazon-payments": "^0.2.8",
|
||||
"amplitude": "^3.5.0",
|
||||
"apidoc": "^0.17.5",
|
||||
"apn": "^2.2.0",
|
||||
"aws-sdk": "^2.597.0",
|
||||
"aws-sdk": "^2.606.0",
|
||||
"bcrypt": "^3.0.7",
|
||||
"body-parser": "^1.18.3",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-session": "^1.3.3",
|
||||
"cookie-session": "^1.4.0",
|
||||
"coupon-code": "^0.4.5",
|
||||
"csv-stringify": "^5.3.6",
|
||||
"cwait": "^1.1.1",
|
||||
@@ -46,7 +46,7 @@
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.24.0",
|
||||
"moment-recur": "^1.0.7",
|
||||
"mongoose": "^5.8.4",
|
||||
"mongoose": "^5.8.9",
|
||||
"morgan": "^1.7.0",
|
||||
"nconf": "^0.10.0",
|
||||
"node-gcm": "^1.0.2",
|
||||
@@ -63,10 +63,10 @@
|
||||
"rimraf": "^3.0.0",
|
||||
"short-uuid": "^3.0.0",
|
||||
"stripe": "^7.15.0",
|
||||
"superagent": "^5.1.3",
|
||||
"superagent": "^5.2.1",
|
||||
"universal-analytics": "^0.4.17",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^3.3.3",
|
||||
"uuid": "^3.4.0",
|
||||
"validator": "^11.0.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"winston": "^2.4.3",
|
||||
@@ -103,14 +103,14 @@
|
||||
"apidoc": "gulp apidoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"axios": "^0.19.1",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chalk": "^2.4.1",
|
||||
"expect.js": "^0.3.1",
|
||||
"istanbul": "^1.1.0-alpha.1",
|
||||
"mocha": "^5.1.1",
|
||||
"monk": "^7.1.1",
|
||||
"monk": "^7.1.2",
|
||||
"require-again": "^2.0.0",
|
||||
"sinon": "^7.2.4",
|
||||
"sinon-chai": "^3.4.0",
|
||||
|
||||
@@ -43,6 +43,7 @@ describe('webhooks', () => {
|
||||
options: {
|
||||
questStarted: true,
|
||||
questFinised: true,
|
||||
questInvited: true,
|
||||
},
|
||||
}, {
|
||||
id: 'userActivity',
|
||||
@@ -576,7 +577,7 @@ describe('webhooks', () => {
|
||||
};
|
||||
});
|
||||
|
||||
['questStarted', 'questFinised'].forEach(type => {
|
||||
['questStarted', 'questFinised', 'questInvited'].forEach(type => {
|
||||
it(`sends ${type} webhooks`, () => {
|
||||
data.type = type;
|
||||
|
||||
|
||||
@@ -1164,6 +1164,23 @@ describe('Group Model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('unlink group tag', async () => {
|
||||
participatingMember.tags.push({
|
||||
name: party.name,
|
||||
id: party._id,
|
||||
group: party._id,
|
||||
});
|
||||
|
||||
await participatingMember.save();
|
||||
await party.leave(participatingMember);
|
||||
|
||||
participatingMember = await User.findOne({ _id: participatingMember._id });
|
||||
const groupTag = participatingMember.tags.find(tag => tag.id === party._id);
|
||||
|
||||
expect(groupTag).to.not.be.undefined;
|
||||
expect(groupTag.group).to.be.undefined;
|
||||
});
|
||||
|
||||
it('deletes a private party when the last member leaves', async () => {
|
||||
await party.leave(participatingMember);
|
||||
await party.leave(sleepingParticipatingMember);
|
||||
|
||||
@@ -183,6 +183,7 @@ describe('Webhook Model', () => {
|
||||
options: {
|
||||
questStarted: true,
|
||||
questFinished: true,
|
||||
questInvited: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -197,6 +198,7 @@ describe('Webhook Model', () => {
|
||||
expect(wh.options).to.eql({
|
||||
questStarted: false,
|
||||
questFinished: false,
|
||||
questInvited: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -210,6 +212,7 @@ describe('Webhook Model', () => {
|
||||
expect(wh.options).to.eql({
|
||||
questStarted: false,
|
||||
questFinished: true,
|
||||
questInvited: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,6 +227,7 @@ describe('Webhook Model', () => {
|
||||
expect(wh.options).to.eql({
|
||||
questStarted: true,
|
||||
questFinished: true,
|
||||
questInvited: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('POST /members/send-private-message', () => {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notAuthorizedToSendMessageToThisUser'),
|
||||
message: t('blockedToSendToThisUser'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ describe('POST /members/transfer-gems', () => {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notAuthorizedToSendMessageToThisUser'),
|
||||
message: t('blockedToSendToThisUser'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
server,
|
||||
sleep,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { quests as questScrolls } from '../../../../../website/common/script/content/quests';
|
||||
@@ -210,5 +211,39 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
|
||||
const returnedGroup = await groupLeader.get(`/groups/${group._id}`);
|
||||
expect(returnedGroup.chat[0]._meta).to.be.undefined;
|
||||
});
|
||||
|
||||
context('sending quest activity webhooks', () => {
|
||||
before(async () => {
|
||||
await server.start();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('sends quest invited webhook', async () => {
|
||||
const uuid = generateUUID();
|
||||
|
||||
await member.post('/user/webhook', {
|
||||
url: `http://localhost:${server.port}/webhooks/${uuid}`,
|
||||
type: 'questActivity',
|
||||
enabled: true,
|
||||
options: {
|
||||
questInvited: true,
|
||||
},
|
||||
});
|
||||
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||
|
||||
await sleep();
|
||||
|
||||
const body = server.getWebhookData(uuid);
|
||||
|
||||
expect(body.type).to.eql('questInvited');
|
||||
expect(body.group.id).to.eql(questingGroup.id);
|
||||
expect(body.group.name).to.eql(questingGroup.name);
|
||||
expect(body.quest.key).to.eql(PET_QUEST);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -226,6 +226,69 @@ describe('POST /user/webhook', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('defaults questActivity options', async () => {
|
||||
body.type = 'questActivity';
|
||||
|
||||
const webhook = await user.post('/user/webhook', body);
|
||||
|
||||
expect(webhook.options).to.eql({
|
||||
questStarted: false,
|
||||
questFinished: false,
|
||||
questInvited: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('can set questActivity options', async () => {
|
||||
body.type = 'questActivity';
|
||||
body.options = {
|
||||
questStarted: true,
|
||||
questFinished: true,
|
||||
questInvited: true,
|
||||
};
|
||||
|
||||
const webhook = await user.post('/user/webhook', body);
|
||||
|
||||
expect(webhook.options).to.eql({
|
||||
questStarted: true,
|
||||
questFinished: true,
|
||||
questInvited: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('discards extra properties in questActivity options', async () => {
|
||||
body.type = 'questActivity';
|
||||
body.options = {
|
||||
questStarted: false,
|
||||
questFinished: true,
|
||||
questInvited: true,
|
||||
foo: 'bar',
|
||||
};
|
||||
|
||||
const webhook = await user.post('/user/webhook', body);
|
||||
|
||||
expect(webhook.options.foo).to.not.exist;
|
||||
expect(webhook.options).to.eql({
|
||||
questStarted: false,
|
||||
questFinished: true,
|
||||
questInvited: true,
|
||||
});
|
||||
});
|
||||
|
||||
['questStarted', 'questFinished', 'questInvited'].forEach(option => {
|
||||
it(`requires questActivity option ${option} to be a boolean`, async () => {
|
||||
body.type = 'questActivity';
|
||||
body.options = {
|
||||
[option]: 'not a boolean',
|
||||
};
|
||||
|
||||
await expect(user.post('/user/webhook', body)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('webhookBooleanOption', { option }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('discards extra properties in globalActivity options', async () => {
|
||||
body.type = 'globalActivity';
|
||||
body.options = {
|
||||
|
||||
@@ -1,6 +1,37 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { configure } from '@storybook/vue';
|
||||
import '../../src/assets/scss/index.scss';
|
||||
import '../../src/assets/css/sprites.css';
|
||||
|
||||
import '../../src/assets/css/sprites/spritesmith-main-0.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-1.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-2.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-3.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-4.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-5.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-6.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-7.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-8.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-9.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-10.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-11.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-12.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-13.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-14.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-15.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-16.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-17.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-18.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-19.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-20.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-21.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-22.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-23.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-24.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-25.css';
|
||||
import '../../src/assets/css/sprites/spritesmith-main-26.css';
|
||||
import Vue from 'vue';
|
||||
import StoreModule from '@/libs/store';
|
||||
|
||||
const req = require.context('../../src', true, /.stories.js$/);
|
||||
|
||||
@@ -8,4 +39,6 @@ function loadStories () {
|
||||
req.keys().forEach(filename => req(filename));
|
||||
}
|
||||
|
||||
Vue.use(StoreModule);
|
||||
|
||||
configure(loadStories, module);
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
export const userStyles = {
|
||||
contributor: {
|
||||
admin: true,
|
||||
level: 9,
|
||||
text: '',
|
||||
},
|
||||
items: {
|
||||
gear: {
|
||||
equipped: {
|
||||
armor: 'armor_special_2',
|
||||
head: 'head_special_2',
|
||||
shield: 'shield_special_goldenknight',
|
||||
headAccessory: 'headAccessory_base_0',
|
||||
eyewear: 'eyewear_base_0',
|
||||
weapon: 'weapon_special_1',
|
||||
back: 'back_base_0',
|
||||
},
|
||||
costume: {
|
||||
armor: 'armor_special_fallRogue',
|
||||
head: 'head_special_fallRogue',
|
||||
shield: 'shield_armoire_shieldOfDiamonds',
|
||||
body: 'body_mystery_201706',
|
||||
eyewear: 'eyewear_special_blackHalfMoon',
|
||||
back: 'back_base_0',
|
||||
headAccessory: 'headAccessory_special_wolfEars',
|
||||
weapon: 'weapon_armoire_lamplighter',
|
||||
},
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
hair: {
|
||||
color: 'black', base: 0, bangs: 3, beard: 0, mustache: 0, flower: 0,
|
||||
},
|
||||
tasks: { groupByChallenge: false, confirmScoreNotes: false },
|
||||
size: 'broad',
|
||||
skin: 'wolf',
|
||||
shirt: 'zombie',
|
||||
chair: 'none',
|
||||
sleep: true,
|
||||
disableClasses: false,
|
||||
background: 'midnight_castle',
|
||||
costume: true,
|
||||
},
|
||||
stats: {
|
||||
buffs: {
|
||||
str: 0,
|
||||
int: 0,
|
||||
per: 0,
|
||||
con: 0,
|
||||
stealth: 0,
|
||||
streaks: false,
|
||||
snowball: false,
|
||||
spookySparkles: false,
|
||||
shinySeed: false,
|
||||
seafoam: false,
|
||||
},
|
||||
training: {
|
||||
int: 0, per: 0, str: 0, con: 0,
|
||||
},
|
||||
hp: 50,
|
||||
mp: 158,
|
||||
exp: 227,
|
||||
gp: 464.31937261345155,
|
||||
lvl: 17,
|
||||
class: 'rogue',
|
||||
points: 17,
|
||||
str: 0,
|
||||
con: 0,
|
||||
int: 0,
|
||||
per: 0,
|
||||
toNextLevel: 380,
|
||||
maxHealth: 50,
|
||||
maxMP: 158,
|
||||
},
|
||||
};
|
||||
@@ -18,46 +18,47 @@
|
||||
"@vue/cli-plugin-router": "^4.1.2",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.1.2",
|
||||
"@vue/cli-service": "^4.1.2",
|
||||
"@storybook/addon-actions": "^5.0.0",
|
||||
"@storybook/addon-knobs": "^5.0.0",
|
||||
"@storybook/addon-links": "^5.0.0",
|
||||
"@storybook/addon-notes": "^5.0.0",
|
||||
"@storybook/vue": "^5.2.5",
|
||||
"@storybook/addon-actions": "^5.3.7",
|
||||
"@storybook/addon-knobs": "^5.3.7",
|
||||
"@storybook/addon-links": "^5.3.7",
|
||||
"@storybook/addon-notes": "^5.3.7",
|
||||
"@storybook/vue": "^5.3.7",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"amplitude-js": "^5.8.0",
|
||||
"axios": "^0.19.0",
|
||||
"axios": "^0.19.1",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"bootstrap": "^4.4.1",
|
||||
"bootstrap-vue": "^2.1.0",
|
||||
"bootstrap-vue": "^2.2.2",
|
||||
"chai": "^4.1.2",
|
||||
"core-js": "^3.6.1",
|
||||
"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.1.2",
|
||||
"habitica-markdown": "^1.3.2",
|
||||
"hellojs": "^1.18.1",
|
||||
"hellojs": "^1.18.4",
|
||||
"inspectpack": "^4.3.0",
|
||||
"intro.js": "^2.9.3",
|
||||
"jquery": "^3.4.1",
|
||||
"lodash": "^4.17.15",
|
||||
"moment": "^2.24.0",
|
||||
"nconf": "^0.10.0",
|
||||
"sass": "^1.24.2",
|
||||
"sass-loader": "^8.0.0",
|
||||
"sass": "^1.25.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"smartbanner.js": "^1.15.0",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"svg-url-loader": "^3.0.3",
|
||||
"svgo": "^1.3.2",
|
||||
"svgo-loader": "^2.2.1",
|
||||
"uuid": "^3.3.3",
|
||||
"uuid": "^3.4.0",
|
||||
"validator": "^11.1.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue-cli-plugin-storybook": "^0.6.1",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
"vue-router": "^3.0.6",
|
||||
"vue-router": "^3.1.5",
|
||||
"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.5"
|
||||
|
||||
@@ -28,7 +28,10 @@
|
||||
</div>
|
||||
<div
|
||||
id="app"
|
||||
:class="{'casting-spell': castingSpell}"
|
||||
:class="{
|
||||
'casting-spell': castingSpell,
|
||||
'resting': showRestingBanner
|
||||
}"
|
||||
>
|
||||
<banned-account-modal />
|
||||
<amazon-payments-modal v-if="!isStaticPage" />
|
||||
@@ -66,7 +69,10 @@
|
||||
</div>
|
||||
<notifications-display />
|
||||
<app-menu />
|
||||
<div class="container-fluid">
|
||||
<div
|
||||
class="container-fluid"
|
||||
:class="{'no-margin': noMargin}"
|
||||
>
|
||||
<app-header />
|
||||
<buyModal
|
||||
:item="selectedItemToBuy || {}"
|
||||
@@ -83,7 +89,7 @@
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
<app-footer />
|
||||
<app-footer v-if="!hideFooter" />
|
||||
<audio
|
||||
id="sound"
|
||||
ref="sound"
|
||||
@@ -97,13 +103,20 @@
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~@/assets/scss/variables.scss';
|
||||
|
||||
#app {
|
||||
height: calc(100% - 56px); /* 56px is the menu */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
|
||||
&.resting {
|
||||
--banner-resting-height: #{$restingToolbarHeight};
|
||||
}
|
||||
|
||||
&.giftingBanner {
|
||||
--banner-gifting-height: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
#loading-screen-inapp {
|
||||
@@ -148,6 +161,13 @@
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.no-margin {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.notification {
|
||||
border-radius: 1000px;
|
||||
background-color: $green-10;
|
||||
@@ -160,7 +180,7 @@
|
||||
|
||||
.resting-banner {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
height: $restingToolbarHeight;
|
||||
background-color: $blue-10;
|
||||
top: 0;
|
||||
z-index: 1300;
|
||||
@@ -302,7 +322,13 @@ export default {
|
||||
return this.$t(`tip${tipNumber}`);
|
||||
},
|
||||
showRestingBanner () {
|
||||
return !this.bannerHidden && this.user.preferences.sleep;
|
||||
return !this.bannerHidden && this.user && this.user.preferences.sleep;
|
||||
},
|
||||
noMargin () {
|
||||
return ['privateMessages'].includes(this.$route.name);
|
||||
},
|
||||
hideFooter () {
|
||||
return ['privateMessages'].includes(this.$route.name);
|
||||
},
|
||||
},
|
||||
created () {
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
.quest_dysheartener {
|
||||
background: url("~@/assets/images/quest_dysheartener.gif") no-repeat;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
.quest_lostMasterclasser4 {
|
||||
background: url("~@/assets/images/quest_lostMasterclasser4.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/quest_lostMasterclasser4.gif") no-repeat;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Veggie {
|
||||
background: url("~@/assets/images/Pet_HatchingPotion_Veggie.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/Pet_HatchingPotion_Veggie.gif") no-repeat;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
@@ -51,7 +45,7 @@
|
||||
|
||||
/* Critical */
|
||||
.weapon_special_critical {
|
||||
background: url("~@/assets/images/weapon_special_critical.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/weapon_special_critical.gif") no-repeat;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
margin-left:-12px;
|
||||
@@ -68,32 +62,32 @@
|
||||
}
|
||||
|
||||
.head_special_0 {
|
||||
background: url("~@/assets/images/BackerOnly-Equip-ShadeHelmet.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Equip-ShadeHelmet.gif") no-repeat;
|
||||
}
|
||||
.head_special_1 {
|
||||
background: url("~@/assets/images/ContributorOnly-Equip-CrystalHelmet.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/ContributorOnly-Equip-CrystalHelmet.gif") no-repeat;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.broad_armor_special_0,.slim_armor_special_0 {
|
||||
background: url("~@/assets/images/BackerOnly-Equip-ShadeArmor.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Equip-ShadeArmor.gif") no-repeat;
|
||||
}
|
||||
.broad_armor_special_1,.slim_armor_special_1 {
|
||||
background: url("~@/assets/images/ContributorOnly-Equip-CrystalArmor.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/ContributorOnly-Equip-CrystalArmor.gif") no-repeat;
|
||||
}
|
||||
|
||||
.shield_special_0 {
|
||||
background: url("~@/assets/images/BackerOnly-Shield-TormentedSkull.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Shield-TormentedSkull.gif") no-repeat;
|
||||
}
|
||||
|
||||
.weapon_special_0 {
|
||||
background: url("~@/assets/images/BackerOnly-Weapon-DarkSoulsBlade.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Weapon-DarkSoulsBlade.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Pet-Wolf-Cerberus {
|
||||
width: 105px;
|
||||
height: 72px;
|
||||
background: url("~@/assets/images/BackerOnly-Pet-CerberusPup.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Pet-CerberusPup.gif") no-repeat;
|
||||
}
|
||||
|
||||
.broad_armor_special_ks2019, .slim_armor_special_ks2019, .eyewear_special_ks2019, .head_special_ks2019, .shield_special_ks2019 {
|
||||
@@ -102,29 +96,29 @@
|
||||
}
|
||||
|
||||
.broad_armor_special_ks2019, .slim_armor_special_ks2019 {
|
||||
background: url("~@/assets/images/BackerOnly-Equip-MythicGryphonArmor.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Equip-MythicGryphonArmor.gif") no-repeat;
|
||||
}
|
||||
|
||||
.eyewear_special_ks2019 {
|
||||
background: url("~@/assets/images/BackerOnly-Equip-MythicGryphonVisor.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Equip-MythicGryphonVisor.gif") no-repeat;
|
||||
}
|
||||
|
||||
.head_special_ks2019 {
|
||||
background: url("~@/assets/images/BackerOnly-Equip-MythicGryphonHelm.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Equip-MythicGryphonHelm.gif") no-repeat;
|
||||
}
|
||||
|
||||
.shield_special_ks2019 {
|
||||
background: url("~@/assets/images/BackerOnly-Equip-MythicGryphonShield.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Equip-MythicGryphonShield.gif") no-repeat;
|
||||
}
|
||||
|
||||
.weapon_special_ks2019 {
|
||||
background: url("~@/assets/images/BackerOnly-Equip-MythicGryphonGlaive.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Equip-MythicGryphonGlaive.gif") no-repeat;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.Pet-Gryphon-Gryphatrice {
|
||||
background: url("~@/assets/images/BackerOnly-Pet-Gryphatrice.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Pet-Gryphatrice.gif") no-repeat;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
@@ -135,11 +129,28 @@
|
||||
}
|
||||
|
||||
.Mount_Head_Gryphon-Gryphatrice {
|
||||
background: url("~@/assets/images/BackerOnly-Mount-Head-Gryphatrice.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Mount-Head-Gryphatrice.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Mount_Body_Gryphon-Gryphatrice {
|
||||
background: url("~@/assets/images/BackerOnly-Mount-Body-Gryphatrice.gif") no-repeat;
|
||||
background: url("~@/assets/images/animated/BackerOnly-Mount-Body-Gryphatrice.gif") no-repeat;
|
||||
}
|
||||
|
||||
.background_airship, .background_clocktower, .background_steamworks {
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
|
||||
.background_airship {
|
||||
background: url("~@/assets/images/animated/background_airship.gif") no-repeat;
|
||||
}
|
||||
|
||||
.background_clocktower {
|
||||
background: url("~@/assets/images/animated/background_clocktower.gif") no-repeat;
|
||||
}
|
||||
|
||||
.background_steamworks {
|
||||
background: url("~@/assets/images/animated/background_steamworks.gif") no-repeat;
|
||||
}
|
||||
|
||||
/* FIXME figure out how to handle customize menu!!
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
.promo_armoire_backgrounds_202001 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -175px;
|
||||
background-position: -424px -175px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_g1g1_2019 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -471px;
|
||||
width: 357px;
|
||||
height: 144px;
|
||||
}
|
||||
.promo_mystery_202001 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -241px -616px;
|
||||
background-position: 0px -619px;
|
||||
width: 279px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_seasonal_shop {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -890px -181px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
.promo_snowballs {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
@@ -24,25 +24,25 @@
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -358px -471px;
|
||||
background-position: -890px -396px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_winter_potions_2020 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -175px;
|
||||
background-position: 0px -323px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_winter_quests_bundle {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -323px;
|
||||
background-position: 0px -175px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_winter_wonderland_2019 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -323px;
|
||||
background-position: 0px -471px;
|
||||
width: 402px;
|
||||
height: 147px;
|
||||
}
|
||||
@@ -52,9 +52,33 @@
|
||||
width: 468px;
|
||||
height: 147px;
|
||||
}
|
||||
.scene_list {
|
||||
.promo_wintery_hair {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -616px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
background-position: -890px -320px;
|
||||
width: 152px;
|
||||
height: 75px;
|
||||
}
|
||||
.promo_wintery_skins {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -424px -323px;
|
||||
width: 420px;
|
||||
height: 147px;
|
||||
}
|
||||
.customize-option.promo_wintery_skins {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -449px -338px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.scene_beffymaroo {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -403px -471px;
|
||||
width: 282px;
|
||||
height: 147px;
|
||||
}
|
||||
.scene_rewards {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -890px 0px;
|
||||
width: 207px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
@@ -1,246 +1,282 @@
|
||||
.shop_shield_warrior_4 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -345px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_shield_warrior_5 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -220px -341px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_healer_0 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -414px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_healer_1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -483px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_healer_2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -552px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_healer_3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -621px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_healer_4 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -828px -1654px;
|
||||
background-position: -1727px -690px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_healer_5 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1573px -486px;
|
||||
background-position: -1727px -759px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_healer_6 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -897px -1654px;
|
||||
background-position: -1658px -690px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_rogue_0 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -966px -1654px;
|
||||
background-position: -1658px -828px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_rogue_1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1035px -1654px;
|
||||
background-position: -1658px -897px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_rogue_2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1104px -1654px;
|
||||
background-position: -1658px -1035px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_rogue_3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1173px -1654px;
|
||||
background-position: -1658px -1104px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_rogue_4 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1242px -1654px;
|
||||
background-position: -1658px -1173px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_rogue_5 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1311px -1654px;
|
||||
background-position: -1658px -1311px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_rogue_6 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1380px -1654px;
|
||||
background-position: -1658px -1449px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_0 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1449px -1654px;
|
||||
background-position: -1658px -1518px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1518px -1654px;
|
||||
background-position: 0px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1587px -1654px;
|
||||
background-position: -69px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1656px -1654px;
|
||||
background-position: -138px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_aetherCrystals {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -276px;
|
||||
background-position: -207px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_bardInstrument {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -414px;
|
||||
background-position: -276px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_critical {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -483px;
|
||||
background-position: -345px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_fencingFoil {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -621px;
|
||||
background-position: -414px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_lunarScythe {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -690px;
|
||||
background-position: -483px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_mammothRiderSpear {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -759px;
|
||||
background-position: -552px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_nomadsScimitar {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -897px;
|
||||
background-position: -621px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_pageBanner {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -1035px;
|
||||
background-position: -690px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_roguishRainbowMessage {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -1104px;
|
||||
background-position: -759px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_skeletonKey {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -1173px;
|
||||
background-position: -828px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_tachi {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -1242px;
|
||||
background-position: -897px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_taskwoodsLantern {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -1311px;
|
||||
background-position: -966px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_special_tridentOfCrashingTides {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -1380px;
|
||||
background-position: -1035px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_warrior_0 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -1449px;
|
||||
background-position: -1104px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_warrior_1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -1518px;
|
||||
background-position: -1173px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_warrior_2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: 0px -1654px;
|
||||
background-position: -1242px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_warrior_3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -69px -1654px;
|
||||
background-position: -1311px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_warrior_4 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -138px -1654px;
|
||||
background-position: -1380px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_warrior_5 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -207px -1654px;
|
||||
background-position: -1449px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_warrior_6 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -276px -1654px;
|
||||
background-position: -1518px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_wizard_0 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -345px -1654px;
|
||||
background-position: -1587px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_wizard_1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -414px -1654px;
|
||||
background-position: -1656px -1654px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_wizard_2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -483px -1654px;
|
||||
background-position: -1727px 0px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_wizard_3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -552px -1654px;
|
||||
background-position: -1727px -69px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_wizard_4 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -621px -1654px;
|
||||
background-position: -1727px -138px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_wizard_5 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -690px -1654px;
|
||||
background-position: -1727px -207px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_wizard_6 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -759px -1654px;
|
||||
background-position: -1727px -276px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
@@ -504,37 +540,37 @@
|
||||
}
|
||||
.Pet_Currency_Gem {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -345px;
|
||||
background-position: -1658px -759px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Currency_Gem1x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1779px -538px;
|
||||
background-position: -1779px -952px;
|
||||
width: 15px;
|
||||
height: 13px;
|
||||
}
|
||||
.Pet_Currency_Gem2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -950px;
|
||||
background-position: -1727px -1364px;
|
||||
width: 30px;
|
||||
height: 26px;
|
||||
}
|
||||
.PixelPaw-Gold {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -538px;
|
||||
background-position: -1727px -952px;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
}
|
||||
.PixelPaw {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -590px;
|
||||
background-position: -1727px -1004px;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
}
|
||||
.PixelPaw002 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -642px;
|
||||
background-position: -1727px -1056px;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
}
|
||||
@@ -588,7 +624,7 @@
|
||||
}
|
||||
.empty_bottles {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -483px;
|
||||
background-position: -1727px -897px;
|
||||
width: 64px;
|
||||
height: 54px;
|
||||
}
|
||||
@@ -600,169 +636,169 @@
|
||||
}
|
||||
.inventory_present {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -660px -504px;
|
||||
background-position: -1100px -944px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_01 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px 0px;
|
||||
background-position: -1582px -245px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_02 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -69px;
|
||||
background-position: -1573px -486px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_03 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -138px;
|
||||
background-position: -220px -203px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_04 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -207px;
|
||||
background-position: -220px -272px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_05 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -276px;
|
||||
background-position: -1727px -828px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_06 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -345px;
|
||||
background-position: -660px -435px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_07 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1582px -245px;
|
||||
background-position: -660px -504px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_08 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -414px;
|
||||
background-position: -660px -573px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_09 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -220px -203px;
|
||||
background-position: -880px -655px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_10 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -220px -272px;
|
||||
background-position: -880px -724px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_11 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -220px -341px;
|
||||
background-position: -880px -793px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_12 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -660px -435px;
|
||||
background-position: -1100px -875px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_birthday {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -660px -573px;
|
||||
background-position: -1100px -1013px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_congrats {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -880px -655px;
|
||||
background-position: -1335px -1095px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_fortify {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -880px -724px;
|
||||
background-position: -1335px -1164px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_getwell {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -880px -793px;
|
||||
background-position: -1582px -1309px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_goodluck {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1100px -875px;
|
||||
background-position: -1574px -1415px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_greeting {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1100px -944px;
|
||||
background-position: -1658px 0px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_nye {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1100px -1013px;
|
||||
background-position: -1658px -69px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_opaquePotion {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1335px -1095px;
|
||||
background-position: -1658px -138px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_seafoam {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1335px -1164px;
|
||||
background-position: -1658px -207px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_shinySeed {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1582px -1309px;
|
||||
background-position: -1658px -276px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_snowball {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1574px -1415px;
|
||||
background-position: -1658px -345px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_spookySparkles {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px 0px;
|
||||
background-position: -1658px -414px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_thankyou {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -69px;
|
||||
background-position: -1658px -483px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_trinket {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -138px;
|
||||
background-position: -1658px -552px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_valentine {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -207px;
|
||||
background-position: -1658px -621px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
@@ -774,13 +810,13 @@
|
||||
}
|
||||
.pet_key {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -552px;
|
||||
background-position: -1658px -966px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.rebirth_orb {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -828px;
|
||||
background-position: -1658px -1242px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
@@ -792,139 +828,139 @@
|
||||
}
|
||||
.shop_armoire {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1658px -966px;
|
||||
background-position: -1658px -1380px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.zzz {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -735px;
|
||||
background-position: -1727px -1149px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.zzz_light {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -694px;
|
||||
background-position: -1727px -1108px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.notif_inventory_present_01 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1756px -863px;
|
||||
background-position: -1756px -1277px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_present_02 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -776px;
|
||||
background-position: -1727px -1190px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_present_03 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -921px;
|
||||
background-position: -1727px -1335px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_present_04 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1756px -892px;
|
||||
background-position: -1756px -1306px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_present_05 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -892px;
|
||||
background-position: -1727px -1306px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_present_06 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1756px -776px;
|
||||
background-position: -1756px -1190px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_present_07 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -863px;
|
||||
background-position: -1727px -1277px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_present_08 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1756px -834px;
|
||||
background-position: -1756px -1248px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_present_09 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -834px;
|
||||
background-position: -1727px -1248px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_present_10 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1756px -805px;
|
||||
background-position: -1756px -1219px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_present_11 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -805px;
|
||||
background-position: -1727px -1219px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_present_12 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1756px -921px;
|
||||
background-position: -1756px -1335px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_inventory_special_birthday {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1748px -977px;
|
||||
background-position: -1748px -1391px;
|
||||
width: 20px;
|
||||
height: 24px;
|
||||
}
|
||||
.notif_inventory_special_congrats {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1769px -977px;
|
||||
background-position: -1769px -1391px;
|
||||
width: 20px;
|
||||
height: 22px;
|
||||
}
|
||||
.notif_inventory_special_getwell {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -1002px;
|
||||
background-position: -1727px -1416px;
|
||||
width: 20px;
|
||||
height: 22px;
|
||||
}
|
||||
.notif_inventory_special_goodluck {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1768px -735px;
|
||||
background-position: -1768px -1149px;
|
||||
width: 20px;
|
||||
height: 26px;
|
||||
}
|
||||
.notif_inventory_special_greeting {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1748px -1002px;
|
||||
background-position: -1748px -1416px;
|
||||
width: 20px;
|
||||
height: 22px;
|
||||
}
|
||||
.notif_inventory_special_nye {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1768px -694px;
|
||||
background-position: -1768px -1108px;
|
||||
width: 24px;
|
||||
height: 26px;
|
||||
}
|
||||
.notif_inventory_special_thankyou {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1727px -977px;
|
||||
background-position: -1727px -1391px;
|
||||
width: 20px;
|
||||
height: 24px;
|
||||
}
|
||||
.notif_inventory_special_valentine {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1758px -950px;
|
||||
background-position: -1758px -1364px;
|
||||
width: 20px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 992 B After Width: | Height: | Size: 992 B |
|
Before Width: | Height: | Size: 872 B After Width: | Height: | Size: 872 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 759 B After Width: | Height: | Size: 759 B |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 687 KiB After Width: | Height: | Size: 687 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 313 KiB After Width: | Height: | Size: 314 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 132 KiB |
@@ -2,8 +2,11 @@
|
||||
// possible values are: normal, fall, habitoween, thanksgiving, winter, nye, birthday, valentines, spring, summer
|
||||
// more to be added on future seasons
|
||||
|
||||
$npc_market_flavor: 'nye';
|
||||
$npc_quests_flavor: 'nye';
|
||||
$npc_seasonal_flavor: 'nye';
|
||||
$npc_market_flavor: 'winter';
|
||||
$npc_quests_flavor: 'winter';
|
||||
$npc_seasonal_flavor: 'winter';
|
||||
$npc_timetravelers_flavor: 'winter';
|
||||
$npc_tavern_flavor: 'nye';
|
||||
$npc_tavern_flavor: 'winter';
|
||||
|
||||
$restingToolbarHeight: 40px;
|
||||
$menuToolbarHeight: 56px;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#A5A1AC" fill-rule="evenodd" d="M15.7 14.3l-4.8-4.8c.7-1 1.1-2.2 1.1-3.5 0-3.3-2.7-6-6-6S0 2.7 0 6s2.7 6 6 6c1.3 0 2.5-.4 3.5-1.1l4.8 4.8c.4.4 1 .4 1.4 0 .4-.4.4-1 0-1.4zM6 10c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 334 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="24" viewBox="0 0 32 24">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="#9A62FF" d="M28 0H4C1.79 0 0 1.79 0 4v16c0 2.21 1.79 4 4 4h24c2.21 0 4-1.79 4-4V4c0-2.21-1.79-4-4-4z"/>
|
||||
<path fill="#BDA8FF" d="M28 20H4V4l12 10L28 4z"/>
|
||||
<path fill="#D5C8FF" d="M28 20H4V7l12 10L28 7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 380 B |
@@ -101,7 +101,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="showCategorySelect && creating"
|
||||
v-if="showCategorySelect"
|
||||
class="category-box"
|
||||
>
|
||||
<!-- eslint-disable vue/no-use-v-if-with-v-for -->
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class="mentioned-icon"
|
||||
></div>
|
||||
<div
|
||||
v-if="!inbox && user.contributor.admin && msg.flagCount"
|
||||
v-if="user.contributor.admin && msg.flagCount"
|
||||
class="message-hidden"
|
||||
>
|
||||
{{ flagCountDescription }}
|
||||
@@ -27,8 +27,7 @@
|
||||
class="mr-1"
|
||||
>•</span>
|
||||
<span
|
||||
v-b-tooltip
|
||||
:title="msg.timestamp | date"
|
||||
v-b-tooltip.hover="messageDate"
|
||||
>{{ msg.timestamp | timeAgo }} </span>
|
||||
<span v-if="msg.client && user.contributor.level >= 4">({{ msg.client }})</span>
|
||||
</p>
|
||||
@@ -37,21 +36,12 @@
|
||||
class="text"
|
||||
v-html="atHighlight(parseMarkdown(msg.text))"
|
||||
></div>
|
||||
<div
|
||||
v-if="isMessageReported && (inbox === true)"
|
||||
class="reported"
|
||||
>
|
||||
<span v-once>{{ $t('reportedMessage') }}</span>
|
||||
<br>
|
||||
<span v-once>{{ $t('canDeleteNow') }}</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div
|
||||
v-if="msg.id"
|
||||
class="d-flex"
|
||||
>
|
||||
<div
|
||||
v-if="!inbox"
|
||||
class="action d-flex align-items-center"
|
||||
@click="copyAsTodo(msg)"
|
||||
>
|
||||
@@ -62,7 +52,7 @@
|
||||
<div>{{ $t('copyAsTodo') }}</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="(inbox || (user.flags.communityGuidelinesAccepted && msg.uuid !== 'system'))
|
||||
v-if="(user.flags.communityGuidelinesAccepted && msg.uuid !== 'system')
|
||||
&& (!isMessageReported || user.contributor.admin)"
|
||||
class="action d-flex align-items-center"
|
||||
@click="report(msg)"
|
||||
@@ -77,7 +67,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="msg.uuid === user._id || inbox || user.contributor.admin"
|
||||
v-if="msg.uuid === user._id || user.contributor.admin"
|
||||
class="action d-flex align-items-center"
|
||||
@click="remove()"
|
||||
>
|
||||
@@ -91,7 +81,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!inbox"
|
||||
v-b-tooltip="{title: likeTooltip(msg.likes[user._id])}"
|
||||
class="ml-auto d-flex"
|
||||
>
|
||||
@@ -121,7 +110,7 @@
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="!msg.likes[user._id] && !inbox">{{ $t('like') }}</span>
|
||||
<span v-if="!msg.likes[user._id]">{{ $t('like') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -205,15 +194,9 @@
|
||||
color: $purple-400;
|
||||
}
|
||||
}
|
||||
|
||||
.reported {
|
||||
margin-top: 18px;
|
||||
color: $red-50;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import escapeRegExp from 'lodash/escapeRegExp';
|
||||
@@ -244,10 +227,6 @@ export default {
|
||||
},
|
||||
props: {
|
||||
msg: {},
|
||||
inbox: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
groupId: {},
|
||||
},
|
||||
data () {
|
||||
@@ -311,6 +290,10 @@ export default {
|
||||
if (this.msg.flagCount < CHAT_FLAG_FROM_SHADOW_MUTE) return 'Message hidden';
|
||||
return 'Message hidden (shadow-muted)';
|
||||
},
|
||||
messageDate () {
|
||||
const date = moment(this.msg.timestamp).toDate();
|
||||
return date.toString();
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
const links = this.$refs.markdownContainer.getElementsByTagName('a');
|
||||
@@ -372,11 +355,6 @@ export default {
|
||||
const message = this.msg;
|
||||
this.$emit('message-removed', message);
|
||||
|
||||
if (this.inbox) {
|
||||
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$store.dispatch('chat:deleteChat', {
|
||||
groupId: this.groupId,
|
||||
chatId: message.id,
|
||||
|
||||
@@ -35,13 +35,11 @@
|
||||
v-for="msg in messages"
|
||||
v-if="chat && canViewFlag(msg)"
|
||||
:key="msg.id"
|
||||
:class="{row: inbox}"
|
||||
>
|
||||
<!-- eslint-enable vue/no-use-v-if-with-v-for -->
|
||||
<div
|
||||
v-if="user._id !== msg.uuid"
|
||||
class="d-flex"
|
||||
:class="{'flex-grow-1': inbox}"
|
||||
>
|
||||
<avatar
|
||||
v-if="msg.userStyles
|
||||
@@ -51,16 +49,13 @@
|
||||
:avatar-only="true"
|
||||
:override-top-padding="'14px'"
|
||||
:hide-class-badge="true"
|
||||
:class="{'inbox-avatar-left': inbox}"
|
||||
@click.native="showMemberModal(msg.uuid)"
|
||||
/>
|
||||
<div
|
||||
class="card"
|
||||
:class="{'col-10': inbox}"
|
||||
>
|
||||
<chat-card
|
||||
:msg="msg"
|
||||
:inbox="inbox"
|
||||
:group-id="groupId"
|
||||
@message-liked="messageLiked"
|
||||
@message-removed="messageRemoved"
|
||||
@@ -72,15 +67,12 @@
|
||||
<div
|
||||
v-if="user._id === msg.uuid"
|
||||
class="d-flex"
|
||||
:class="{'flex-grow-1': inbox}"
|
||||
>
|
||||
<div
|
||||
class="card"
|
||||
:class="{'col-10': inbox}"
|
||||
>
|
||||
<chat-card
|
||||
:msg="msg"
|
||||
:inbox="inbox"
|
||||
:group-id="groupId"
|
||||
@message-liked="messageLiked"
|
||||
@message-removed="messageRemoved"
|
||||
@@ -95,7 +87,6 @@
|
||||
:avatar-only="true"
|
||||
:hide-class-badge="true"
|
||||
:override-top-padding="'14px'"
|
||||
:class="{'inbox-avatar-right': inbox}"
|
||||
@click.native="showMemberModal(msg.uuid)"
|
||||
/>
|
||||
</div>
|
||||
@@ -144,16 +135,6 @@
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.inbox-avatar-left {
|
||||
margin-left: -1rem;
|
||||
margin-right: 2.5rem;
|
||||
min-width: 5rem;
|
||||
}
|
||||
|
||||
.inbox-avatar-right {
|
||||
margin-left: -3.5rem;
|
||||
}
|
||||
|
||||
.hr {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
@@ -209,10 +190,6 @@ export default {
|
||||
},
|
||||
props: {
|
||||
chat: {},
|
||||
inbox: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
groupType: {},
|
||||
groupId: {},
|
||||
groupName: {},
|
||||
@@ -260,12 +237,6 @@ export default {
|
||||
this.lastOffset = container.scrollTop - (container.scrollHeight - container.clientHeight);
|
||||
// disable scroll
|
||||
container.style.overflowY = 'hidden';
|
||||
|
||||
const canLoadMore = this.inbox && !this.isLoading && this.canLoadMore;
|
||||
if (canLoadMore) {
|
||||
await this.$emit('triggerLoad');
|
||||
this.handleScrollBack = true;
|
||||
}
|
||||
},
|
||||
canViewFlag (message) {
|
||||
if (message.uuid === this.user._id) return true;
|
||||
@@ -380,11 +351,6 @@ export default {
|
||||
this.chat.splice(chatIndex, 1, message);
|
||||
},
|
||||
messageRemoved (message) {
|
||||
if (this.inbox) {
|
||||
this.$emit('message-removed', message);
|
||||
return;
|
||||
}
|
||||
|
||||
const chatIndex = findIndex(this.chat, chatMessage => chatMessage.id === message.id);
|
||||
this.chat.splice(chatIndex, 1);
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
<div class="footer text-center">
|
||||
<button
|
||||
v-if="user.contributor.admin && abuseObject.flagCount > 0"
|
||||
v-if="user.contributor.admin"
|
||||
class="pull-left btn btn-danger"
|
||||
@click="clearFlagCount()"
|
||||
>
|
||||
|
||||
@@ -197,6 +197,59 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!filterBackgrounds"
|
||||
class="row text-center title-row"
|
||||
>
|
||||
<strong>{{ backgroundShopSets[1].text }}</strong>
|
||||
</div>
|
||||
<div
|
||||
v-if="!filterBackgrounds"
|
||||
class="row title-row"
|
||||
>
|
||||
<div
|
||||
v-for="bg in backgroundShopSets[1].items"
|
||||
:key="bg.key"
|
||||
class="col-4 text-center customize-option background-button"
|
||||
:popover-title="bg.text"
|
||||
:popover="bg.notes"
|
||||
popover-trigger="mouseenter"
|
||||
@click="!user.purchased.background[bg.key]
|
||||
? backgroundSelected(bg) : unlock('background.' + bg.key)"
|
||||
>
|
||||
<div
|
||||
class="background"
|
||||
:class="[`background_${bg.key}`, backgroundLockedStatus(bg.key)]"
|
||||
></div>
|
||||
<i
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="glyphicon glyphicon-lock"
|
||||
></i>
|
||||
<div
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="purchase-background single"
|
||||
>
|
||||
<div
|
||||
class="svg-icon hourglass"
|
||||
v-html="icons.hourglass"
|
||||
></div>
|
||||
<span class="price">1</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="!user.purchased.background[bg.key]"
|
||||
class="badge badge-pill badge-item badge-svg"
|
||||
:class="{
|
||||
'item-selected-badge': isBackgroundPinned(bg),
|
||||
'hide': !isBackgroundPinned(bg)}"
|
||||
@click.prevent.stop="togglePinned(bg)"
|
||||
>
|
||||
<span
|
||||
class="svg-icon inline icon-12 color"
|
||||
v-html="icons.pin"
|
||||
></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<sub-menu
|
||||
class="text-center"
|
||||
:items="bgSubMenuItems"
|
||||
@@ -923,7 +976,7 @@
|
||||
color: #24cc8f;
|
||||
}
|
||||
|
||||
.gem, .coin {
|
||||
.gem, .coin, .hourglass {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
@@ -944,7 +997,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.gem, .coin {
|
||||
.gem, .coin, .hourglass {
|
||||
margin: 0 .5em;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
@@ -1103,6 +1156,7 @@ import skinIcon from '@/assets/svg/skin.svg';
|
||||
import hairIcon from '@/assets/svg/hair.svg';
|
||||
import backgroundsIcon from '@/assets/svg/backgrounds.svg';
|
||||
import gem from '@/assets/svg/gem.svg';
|
||||
import hourglass from '@/assets/svg/hourglass.svg';
|
||||
import gold from '@/assets/svg/gold.svg';
|
||||
import pin from '@/assets/svg/pin.svg';
|
||||
import arrowRight from '@/assets/svg/arrow_right.svg';
|
||||
@@ -1143,6 +1197,7 @@ export default {
|
||||
hairIcon,
|
||||
backgroundsIcon,
|
||||
gem,
|
||||
hourglass,
|
||||
pin,
|
||||
gold,
|
||||
arrowRight,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { storiesOf } from '@storybook/vue';
|
||||
|
||||
import FaceAvatar from './faceAvatar.vue';
|
||||
import Avatar from './avatar.vue';
|
||||
import { userStyles } from '../../config/storybook/mock.data';
|
||||
import content from '../../../common/script/content/index';
|
||||
import getters from '@/store/getters';
|
||||
|
||||
|
||||
storiesOf('Face Avatar', module)
|
||||
.add('simple', () => ({
|
||||
components: { FaceAvatar },
|
||||
template: `
|
||||
<div style="position: absolute; margin: 20px">
|
||||
<face-avatar :member="user"></face-avatar>
|
||||
</div>
|
||||
`,
|
||||
data () {
|
||||
return {
|
||||
user: userStyles,
|
||||
};
|
||||
},
|
||||
}))
|
||||
.add('compare', () => ({
|
||||
components: { FaceAvatar, Avatar },
|
||||
template: `
|
||||
<div style="position: absolute; margin: 20px">
|
||||
<face-avatar :member="user"></face-avatar>
|
||||
<avatar :member="user"></avatar>
|
||||
</div>
|
||||
`,
|
||||
data () {
|
||||
return {
|
||||
user: userStyles,
|
||||
};
|
||||
},
|
||||
state: {
|
||||
content,
|
||||
},
|
||||
store: {
|
||||
getters,
|
||||
state: {
|
||||
content,
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<div
|
||||
class="face-avatar"
|
||||
:style="{width, height}"
|
||||
>
|
||||
<div class="character-sprites">
|
||||
<!-- Buffs that cause visual changes to avatar: Snowman, Ghost, Flower, etc-->
|
||||
<template v-for="(klass, item) in visualBuffs">
|
||||
<span
|
||||
v-if="member.stats.buffs[item] && showVisualBuffs"
|
||||
:key="klass"
|
||||
:class="klass"
|
||||
></span>
|
||||
</template>
|
||||
<!-- Show flower ALL THE TIME!!!-->
|
||||
<!-- See https://github.com/HabitRPG/habitica/issues/7133-->
|
||||
<span :class="'hair_flower_' + member.preferences.hair.flower"></span>
|
||||
<!-- Show avatar only if not currently affected by visual buff-->
|
||||
<template v-if="showAvatar()">
|
||||
<span :class="[skinClass]"></span><span :class="['head_0']"></span>
|
||||
<template v-for="type in ['bangs', 'base', 'mustache', 'beard']">
|
||||
<span
|
||||
:key="type"
|
||||
:class="[getHairClass(type)]"
|
||||
></span>
|
||||
</template>
|
||||
<span :class="[getGearClass('body')]"></span>
|
||||
<span :class="[getGearClass('eyewear')]"></span>
|
||||
<span :class="[getGearClass('head')]"></span>
|
||||
<span :class="[getGearClass('headAccessory')]"></span>
|
||||
<span :class="['hair_flower_' + member.preferences.hair.flower]"></span>
|
||||
</template>
|
||||
<!-- Resting--><span
|
||||
v-if="member.preferences.sleep"
|
||||
class="zzz"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.face-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: solid 2px currentColor;
|
||||
border-radius: 18px;
|
||||
image-rendering: pixelated;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.character-sprites {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
margin: -25px -41px;
|
||||
}
|
||||
|
||||
.character-sprites span {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
member: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
avatarOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
hideClassBadge: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
withBackground: {
|
||||
type: Boolean,
|
||||
},
|
||||
overrideAvatarGear: {
|
||||
type: Object,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 140,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 147,
|
||||
},
|
||||
showVisualBuffs: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
flatGear: 'content.gear.flat',
|
||||
}),
|
||||
hasClass () {
|
||||
return this.$store.getters['members:hasClass'](this.member);
|
||||
},
|
||||
isBuffed () {
|
||||
return this.$store.getters['members:isBuffed'](this.member);
|
||||
},
|
||||
visualBuffs () {
|
||||
return {
|
||||
snowball: 'snowman',
|
||||
spookySparkles: 'ghost',
|
||||
shinySeed: `avatar_floral_${this.member.stats.class}`,
|
||||
seafoam: 'seafoam_star',
|
||||
};
|
||||
},
|
||||
skinClass () {
|
||||
const baseClass = `skin_${this.member.preferences.skin}`;
|
||||
|
||||
return `${baseClass}${this.member.preferences.sleep ? '_sleep' : ''}`;
|
||||
},
|
||||
costumeClass () {
|
||||
return this.member.preferences.costume ? 'costume' : 'equipped';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getGearClass (gearType) {
|
||||
let result = this.member.items.gear[this.costumeClass][gearType];
|
||||
|
||||
if (this.overrideAvatarGear && this.overrideAvatarGear[gearType]) {
|
||||
result = this.overrideAvatarGear[gearType];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
showAvatar () {
|
||||
if (!this.showVisualBuffs) return true;
|
||||
|
||||
const { buffs } = this.member.stats;
|
||||
|
||||
return !buffs.snowball && !buffs.spookySparkles && !buffs.shinySeed && !buffs.seafoam;
|
||||
},
|
||||
getHairClass (type) {
|
||||
const hairPref = this.member.preferences.hair;
|
||||
|
||||
return `hair_${type}_${hairPref[type]}_${hairPref.color}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -536,13 +536,12 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
sendMessage (member) {
|
||||
this.$root.$emit('habitica::new-inbox-message', {
|
||||
userIdToMessage: member._id,
|
||||
displayName: member.profile.name,
|
||||
username: member.auth.local.username,
|
||||
backer: member.backer,
|
||||
contributor: member.contributor,
|
||||
this.$store.dispatch('user:newPrivateMessageTo', {
|
||||
member,
|
||||
});
|
||||
|
||||
this.$root.$emit('bv::hide::modal', 'members-modal');
|
||||
this.$router.push('/private-messages');
|
||||
},
|
||||
async searchMembers (searchTerm = '') {
|
||||
this.members = await this.$store.state.memberModalOptions.fetchMoreMembers({
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div
|
||||
id="app-header"
|
||||
class="row"
|
||||
:class="{'hide-header': $route.name === 'groupPlan'}"
|
||||
:class="{'hide-header': hideHeader}"
|
||||
>
|
||||
<members-modal :hide-badge="true" />
|
||||
<member-details
|
||||
@@ -171,6 +171,9 @@ export default {
|
||||
sortedPartyMembers () {
|
||||
return orderBy(this.partyMembers, [this.user.party.order], [this.user.party.orderAscending]);
|
||||
},
|
||||
hideHeader () {
|
||||
return ['groupPlan', 'privateMessages'].includes(this.$route.name);
|
||||
},
|
||||
},
|
||||
created () {
|
||||
if (this.user.party && this.user.party._id) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<inbox-modal />
|
||||
<creator-intro />
|
||||
<profileModal />
|
||||
<report-flag-modal />
|
||||
@@ -408,6 +407,7 @@
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~@/assets/scss/utils.scss';
|
||||
@import '~@/assets/scss/variables.scss';
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.chevron {
|
||||
@@ -438,7 +438,7 @@
|
||||
}
|
||||
|
||||
.topbar {
|
||||
max-height: 56px;
|
||||
max-height: $menuToolbarHeight;
|
||||
|
||||
.currency-tray {
|
||||
margin-left: auto;
|
||||
@@ -721,7 +721,6 @@ import chevronDownIcon from '@/assets/svg/chevron-down.svg';
|
||||
import logo from '@/assets/svg/logo.svg';
|
||||
|
||||
import creatorIntro from '../creatorIntro';
|
||||
import InboxModal from '../userMenu/inbox.vue';
|
||||
import notificationMenu from './notificationsDropdown';
|
||||
import profileModal from '../userMenu/profileModal';
|
||||
import reportFlagModal from '../chat/reportFlagModal';
|
||||
@@ -733,7 +732,6 @@ import userDropdown from './userDropdown';
|
||||
export default {
|
||||
components: {
|
||||
creatorIntro,
|
||||
InboxModal,
|
||||
notificationMenu,
|
||||
profileModal,
|
||||
reportFlagModal,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
:can-remove="canRemove"
|
||||
:has-icon="true"
|
||||
:notification="notification"
|
||||
:read-after-click="true"
|
||||
:read-after-click="false"
|
||||
@click="action"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -25,7 +25,7 @@ export default {
|
||||
props: ['notification', 'canRemove'],
|
||||
methods: {
|
||||
action () {
|
||||
this.$root.$emit('bv::show::modal', 'inbox-modal');
|
||||
this.$router.push('/private-messages');
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -139,7 +139,7 @@ import GROUP_TASK_CLAIMED from './notifications/groupTaskClaimed';
|
||||
import UNALLOCATED_STATS_POINTS from './notifications/unallocatedStatsPoints';
|
||||
import NEW_MYSTERY_ITEMS from './notifications/newMysteryItems';
|
||||
import CARD_RECEIVED from './notifications/cardReceived';
|
||||
import NEW_INBOX_MESSAGE from './notifications/newInboxMessage';
|
||||
import NEW_INBOX_MESSAGE from './notifications/newPrivateMessage';
|
||||
import NEW_CHAT_MESSAGE from './notifications/newChatMessage';
|
||||
import WORLD_BOSS from './notifications/worldBoss';
|
||||
import VERIFY_USERNAME from './notifications/verifyUsername';
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<a
|
||||
class="nav-link dropdown-item
|
||||
dropdown-separated d-flex justify-content-between align-items-center"
|
||||
@click.prevent="showInbox()"
|
||||
@click.prevent="showPrivateMessages()"
|
||||
>
|
||||
<div>{{ $t('messages') }}</div>
|
||||
<message-count
|
||||
@@ -43,7 +43,7 @@
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
@click="showAvatar('backgrounds', '2019')"
|
||||
@click="showAvatar('backgrounds', '2020')"
|
||||
>{{ $t('backgrounds') }}</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
@@ -163,10 +163,15 @@ export default {
|
||||
this.$store.state.avatarEditorOptions.subpage = subpage;
|
||||
this.$root.$emit('bv::show::modal', 'avatar-modal');
|
||||
},
|
||||
showInbox () {
|
||||
showPrivateMessages () {
|
||||
markPMSRead(this.user);
|
||||
axios.post('/api/v4/user/mark-pms-read');
|
||||
this.$root.$emit('bv::show::modal', 'inbox-modal');
|
||||
|
||||
if (this.$router.history.current.name === 'privateMessages') {
|
||||
this.$root.$emit('pm::refresh');
|
||||
} else {
|
||||
this.$router.push('/private-messages');
|
||||
}
|
||||
},
|
||||
showProfile (startingPage) {
|
||||
this.$router.push({ name: startingPage });
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div
|
||||
class="conversation"
|
||||
:class="{active: activeKey === uuid}"
|
||||
@click="$emit('click', {})"
|
||||
@mouseleave="hideDropDown()"
|
||||
>
|
||||
<div class="user">
|
||||
<user-label
|
||||
: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"
|
||||
>
|
||||
<b-dropdown
|
||||
ref="dropdown"
|
||||
class="action-dropdown"
|
||||
right
|
||||
toggle-class="btn-flat action-padding"
|
||||
no-caret
|
||||
variant="link"
|
||||
size="lg"
|
||||
>
|
||||
<template slot="button-content">
|
||||
<div
|
||||
class="svg-icon inline dots"
|
||||
v-html="icons.dots"
|
||||
></div>
|
||||
</template>
|
||||
<b-dropdown-item @click="block()">
|
||||
<span class="dropdown-icon-item">
|
||||
<div
|
||||
class="svg-icon inline"
|
||||
v-html="icons.remove"
|
||||
></div><span class="text">{{ $t('block') }}</span></span>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import userLabel from '../userLabel';
|
||||
|
||||
import dots from '@/assets/svg/dots.svg';
|
||||
import remove from '@/assets/svg/remove.svg';
|
||||
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
userLabel,
|
||||
},
|
||||
props: [
|
||||
'activeKey', 'uuid', 'backer', 'displayName',
|
||||
'username', 'contributor', 'lastMessageText',
|
||||
'lastMessageDate',
|
||||
],
|
||||
computed: {
|
||||
...mapState({
|
||||
userLoggedIn: 'user.data',
|
||||
}),
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
dots,
|
||||
remove,
|
||||
}),
|
||||
};
|
||||
},
|
||||
filters: {
|
||||
timeAgo (value) {
|
||||
return moment(value).fromNow();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hideDropDown () {
|
||||
const { dropdown } = this.$refs;
|
||||
|
||||
if (dropdown) {
|
||||
dropdown.hide();
|
||||
}
|
||||
},
|
||||
block () {
|
||||
this.$store.dispatch('user:block', {
|
||||
uuid: this.uuid,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.action-padding {
|
||||
height: 24px !important;
|
||||
width: 24px;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.action-dropdown {
|
||||
.dropdown-item {
|
||||
padding: 12px 16px;
|
||||
|
||||
.svg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.dots {
|
||||
height: 16px;
|
||||
width: 4px;
|
||||
|
||||
svg path {
|
||||
fill: $purple-300
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.conversation {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid $gray-500;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: #EEE;
|
||||
|
||||
.actions {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #f1edff;
|
||||
}
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 20px;
|
||||
|
||||
.user-label {
|
||||
flex: 1;
|
||||
flex-grow: 0;
|
||||
margin-right: 0.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.username {
|
||||
flex: 1;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.time {
|
||||
flex: 2;
|
||||
text-align: end;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.messagePreview {
|
||||
//width: 100%;
|
||||
height: 30px;
|
||||
margin-right: 40px;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-stretch: normal;
|
||||
line-height: 1.33;
|
||||
letter-spacing: normal;
|
||||
color: $gray-100;
|
||||
overflow: hidden;
|
||||
|
||||
// text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
|
||||
.messagePreview {
|
||||
flex: 1;
|
||||
width: calc(100% - 16px);
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
display: none;
|
||||
width: 16px;
|
||||
margin-top: 4px;
|
||||
|
||||
.dots {
|
||||
height: 16px;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<div class="card-body">
|
||||
<user-link
|
||||
:user-id="msg.uuid"
|
||||
:name="msg.user"
|
||||
:backer="msg.backer"
|
||||
:contributor="msg.contributor"
|
||||
/>
|
||||
<p class="time">
|
||||
<span
|
||||
v-if="msg.username"
|
||||
class="mr-1"
|
||||
>@{{ msg.username }}</span><span
|
||||
v-if="msg.username"
|
||||
class="mr-1"
|
||||
>•</span>
|
||||
<span
|
||||
v-b-tooltip.hover="messageDate"
|
||||
>{{ msg.timestamp | timeAgo }} </span>
|
||||
<span v-if="msg.client && user.contributor.level >= 4"> ({{ msg.client }})</span>
|
||||
</p>
|
||||
<div
|
||||
class="text"
|
||||
v-html="atHighlight(parseMarkdown(msg.text))"
|
||||
></div>
|
||||
<div
|
||||
v-if="isMessageReported"
|
||||
class="reported"
|
||||
>
|
||||
<span v-once>{{ $t('reportedMessage') }}</span><br>
|
||||
<span v-once>{{ $t('canDeleteNow') }}</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div
|
||||
v-if="msg.id"
|
||||
class="d-flex"
|
||||
>
|
||||
<div
|
||||
v-if="!isMessageReported"
|
||||
class="action d-flex align-items-center"
|
||||
@click="report(msg)"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon"
|
||||
v-html="icons.report"
|
||||
></div>
|
||||
<div v-once>
|
||||
{{ $t('report') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="action d-flex align-items-center"
|
||||
@click="remove()"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon"
|
||||
v-html="icons.delete"
|
||||
></div>
|
||||
<div v-once>
|
||||
{{ $t('delete') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.at-highlight {
|
||||
background-color: rgba(213, 200, 255, 0.32);
|
||||
padding: 0.1rem;
|
||||
}
|
||||
|
||||
.at-text {
|
||||
color: #6133b4;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~@/assets/scss/tiers.scss';
|
||||
|
||||
.action {
|
||||
display: inline-block;
|
||||
color: $gray-200;
|
||||
margin-right: 1em;
|
||||
font-size: 12px;
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
color: $gray-300;
|
||||
margin-right: .2em;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
color: $purple-300;
|
||||
|
||||
.svg-icon {
|
||||
color: $purple-400;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 0.75rem 1.25rem 0.75rem 1.25rem;
|
||||
|
||||
.time {
|
||||
font-size: 12px;
|
||||
color: $gray-200;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 14px;
|
||||
color: $gray-50;
|
||||
text-align: left !important;
|
||||
min-height: 0rem;
|
||||
margin-bottom: -0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.reported {
|
||||
margin-top: 18px;
|
||||
color: $red-50;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
|
||||
import habiticaMarkdown from 'habitica-markdown';
|
||||
import { mapState } from '@/libs/store';
|
||||
import userLink from '../userLink';
|
||||
|
||||
import deleteIcon from '@/assets/svg/delete.svg';
|
||||
import reportIcon from '@/assets/svg/report.svg';
|
||||
import { highlightUsers } from '../../libs/highlightUsers';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
userLink,
|
||||
},
|
||||
filters: {
|
||||
timeAgo (value) {
|
||||
return moment(value).fromNow();
|
||||
},
|
||||
},
|
||||
props: {
|
||||
msg: {},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
delete: deleteIcon,
|
||||
report: reportIcon,
|
||||
}),
|
||||
reported: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
isMessageReported () {
|
||||
return (this.msg.flags && this.msg.flags[this.user.id]) || this.reported;
|
||||
},
|
||||
messageDate () {
|
||||
const date = moment(this.msg.timestamp).toDate();
|
||||
return date.toString();
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.$emit('message-card-mounted');
|
||||
},
|
||||
methods: {
|
||||
report () {
|
||||
this.$root.$on('habitica:report-result', data => {
|
||||
if (data.ok) {
|
||||
this.reported = true;
|
||||
}
|
||||
|
||||
this.$root.$off('habitica:report-result');
|
||||
});
|
||||
|
||||
this.$root.$emit('habitica::report-chat', {
|
||||
message: this.msg,
|
||||
groupId: 'privateMessage',
|
||||
});
|
||||
},
|
||||
async remove () {
|
||||
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) return;
|
||||
|
||||
const message = this.msg;
|
||||
this.$emit('message-removed', message);
|
||||
|
||||
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
|
||||
},
|
||||
atHighlight (text) {
|
||||
return highlightUsers(text, this.user.auth.local.username, this.user.profile.name);
|
||||
},
|
||||
parseMarkdown (text) {
|
||||
if (!text) return null;
|
||||
return habiticaMarkdown.render(String(text));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,400 @@
|
||||
<template>
|
||||
<perfect-scrollbar
|
||||
ref="container"
|
||||
class="container-fluid"
|
||||
:class="{'disable-perfect-scroll': disablePerfectScroll}"
|
||||
:options="psOptions"
|
||||
>
|
||||
<div class="row loadmore">
|
||||
<div v-if="canLoadMore && !isLoading">
|
||||
<div class="loadmore-divider"></div>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="triggerLoad()"
|
||||
>
|
||||
{{ $t('loadEarlierMessages') }}
|
||||
</button>
|
||||
<div class="loadmore-divider"></div>
|
||||
</div>
|
||||
<h2
|
||||
v-show="isLoading"
|
||||
class="col-12 loading"
|
||||
>
|
||||
{{ $t('loading') }}
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
v-for="(msg) in messages"
|
||||
:key="msg.id"
|
||||
class="row message-row"
|
||||
:class="{ 'margin-right': user._id !== msg.uuid}"
|
||||
>
|
||||
<div
|
||||
v-if="user._id !== msg.uuid"
|
||||
class="d-flex flex-grow-1"
|
||||
>
|
||||
<avatar
|
||||
v-if="msg.userStyles || (cachedProfileData[msg.uuid]
|
||||
&& !cachedProfileData[msg.uuid].rejected)"
|
||||
class="avatar-left"
|
||||
:member="msg.userStyles || cachedProfileData[msg.uuid]"
|
||||
:avatar-only="true"
|
||||
:override-top-padding="'14px'"
|
||||
:hide-class-badge="true"
|
||||
@click.native="showMemberModal(msg.uuid)"
|
||||
/>
|
||||
<div class="card card-right">
|
||||
<message-card
|
||||
:msg="msg"
|
||||
@message-removed="messageRemoved"
|
||||
@show-member-modal="showMemberModal"
|
||||
@message-card-mounted="itemWasMounted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="user._id === msg.uuid"
|
||||
class="d-flex flex-grow-1"
|
||||
>
|
||||
<div class="card card-left">
|
||||
<message-card
|
||||
:msg="msg"
|
||||
@message-removed="messageRemoved"
|
||||
@show-member-modal="showMemberModal"
|
||||
@message-card-mounted="itemWasMounted"
|
||||
/>
|
||||
</div>
|
||||
<avatar
|
||||
v-if="msg.userStyles
|
||||
|| (cachedProfileData[msg.uuid] && !cachedProfileData[msg.uuid].rejected)"
|
||||
class="avatar-right"
|
||||
:member="msg.userStyles || cachedProfileData[msg.uuid]"
|
||||
:avatar-only="true"
|
||||
:hide-class-badge="true"
|
||||
:override-top-padding="'14px'"
|
||||
@click.native="showMemberModal(msg.uuid)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</perfect-scrollbar>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css';
|
||||
|
||||
.disable-perfect-scroll {
|
||||
overflow-y: inherit !important;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 15%;
|
||||
min-width: 8rem;
|
||||
height: 120px;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.avatar-left {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
.avatar-right {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 0px;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0rem;
|
||||
width: 684px;
|
||||
}
|
||||
.message-row {
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
|
||||
&:not(.margin-right) {
|
||||
.d-flex {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.card {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1400px) {
|
||||
.message-row {
|
||||
margin-left: -15px;
|
||||
margin-right: -30px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-left {
|
||||
border: 1px solid $purple-500;
|
||||
}
|
||||
|
||||
.card-right {
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
|
||||
.hr {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
border-bottom: 1px solid $gray-500;
|
||||
text-align: center;
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
.hr-middle {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-family: 'Roboto Condensed';
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
color: $gray-200;
|
||||
background-color: $gray-700;
|
||||
padding: .2em;
|
||||
margin-top: .2em;
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.loadmore {
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
text-align: center;
|
||||
color: $gray-50;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loadmore-divider {
|
||||
height: 1px;
|
||||
background-color: $gray-500;
|
||||
flex: 1;
|
||||
margin-left: 24px;
|
||||
margin-right: 24px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
padding-left: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<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';
|
||||
|
||||
import Avatar from '../avatar';
|
||||
import messageCard from './messageCard';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Avatar,
|
||||
messageCard,
|
||||
PerfectScrollbar,
|
||||
},
|
||||
props: {
|
||||
chat: {},
|
||||
isLoading: Boolean,
|
||||
canLoadMore: Boolean,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
currentDayDividerDisplay: moment().day(),
|
||||
cachedProfileData: {},
|
||||
currentProfileLoadedCount: 0,
|
||||
currentProfileLoadedEnd: 10,
|
||||
loading: false,
|
||||
handleScrollBack: false,
|
||||
lastOffset: -1,
|
||||
disablePerfectScroll: false,
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.loadProfileCache();
|
||||
|
||||
this.$el.addEventListener('selectstart', () => this.handleSelectStart());
|
||||
this.$el.addEventListener('mouseup', () => this.handleSelectChange());
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('scroll', this.handleScroll);
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$el.removeEventListener('selectstart', () => this.handleSelectStart());
|
||||
this.$el.removeEventListener('mouseup', () => this.handleSelectChange());
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('scroll', this.handleScroll);
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
// @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 () {
|
||||
return {
|
||||
suppressScrollX: true,
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleScroll () {
|
||||
this.loadProfileCache(window.scrollY / 1000);
|
||||
},
|
||||
async triggerLoad () {
|
||||
const container = this.$refs.container.$el;
|
||||
|
||||
// get current offset
|
||||
this.lastOffset = container.scrollTop - (container.scrollHeight - container.clientHeight);
|
||||
// disable scroll
|
||||
// container.style.overflowY = 'hidden';
|
||||
|
||||
const canLoadMore = !this.isLoading && this.canLoadMore;
|
||||
|
||||
if (canLoadMore) {
|
||||
const triggerLoadResult = this.$emit('triggerLoad');
|
||||
|
||||
await triggerLoadResult;
|
||||
|
||||
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();
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
itemWasMounted: debounce(function itemWasMounted () {
|
||||
if (this.handleScrollBack) {
|
||||
this.handleScrollBack = false;
|
||||
|
||||
const container = this.$refs.container.$el;
|
||||
const offset = container.scrollHeight - container.clientHeight;
|
||||
|
||||
const newOffset = offset + this.lastOffset;
|
||||
|
||||
container.scrollTo(0, newOffset);
|
||||
// enable scroll again
|
||||
// container.style.overflowY = 'scroll';
|
||||
}
|
||||
}, 50),
|
||||
messageRemoved (message) {
|
||||
this.$emit('message-removed', message);
|
||||
},
|
||||
handleSelectStart () {
|
||||
this.disablePerfectScroll = true;
|
||||
},
|
||||
handleSelectChange () {
|
||||
this.disablePerfectScroll = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,14 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="d-flex justify-content-around">
|
||||
<span
|
||||
v-for="currency of currencies"
|
||||
:key="currency.key"
|
||||
>
|
||||
<div
|
||||
class="svg-icon"
|
||||
class="svg-icon ml-1"
|
||||
v-html="currency.icon"
|
||||
></div>
|
||||
<span :class="{'notEnough': currency.notEnough}">{{ currency.value | roundBigNumber }}</span>
|
||||
<span
|
||||
:class="{'notEnough': currency.notEnough}"
|
||||
class="mx-1"
|
||||
>
|
||||
{{ currency.value | roundBigNumber }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -29,9 +34,6 @@ span {
|
||||
vertical-align: middle;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 8px;
|
||||
margin-left: 4px;
|
||||
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@@ -51,9 +53,6 @@ import currencyMixin from './_currencyMixin';
|
||||
export default {
|
||||
mixins: [currencyMixin],
|
||||
props: {
|
||||
withHourglass: {
|
||||
type: Boolean,
|
||||
},
|
||||
currencyNeeded: {
|
||||
type: String,
|
||||
},
|
||||
@@ -73,14 +72,11 @@ export default {
|
||||
computed: {
|
||||
currencies () {
|
||||
const currencies = [];
|
||||
|
||||
if (this.withHourglass) {
|
||||
currencies.push({
|
||||
type: 'hourglasses',
|
||||
icon: this.icons.hourglasses,
|
||||
value: this.userHourglasses,
|
||||
});
|
||||
}
|
||||
currencies.push({
|
||||
type: 'hourglasses',
|
||||
icon: this.icons.hourglasses,
|
||||
value: this.userHourglasses,
|
||||
});
|
||||
|
||||
currencies.push({
|
||||
type: 'gems',
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
@click.prevent.stop="togglePinned()"
|
||||
>
|
||||
<span
|
||||
class="svg-icon inline color icon-10"
|
||||
class="svg-icon inline color icon-16"
|
||||
v-html="icons.pin"
|
||||
></span>
|
||||
</span>
|
||||
<div class="close">
|
||||
<div>
|
||||
<span
|
||||
class="svg-icon inline icon-10"
|
||||
class="svg-icon icon-12 close-icon"
|
||||
aria-hidden="true"
|
||||
@click="hideDialog()"
|
||||
v-html="icons.close"
|
||||
@@ -54,10 +54,7 @@
|
||||
<h4 class="title">
|
||||
{{ itemText }}
|
||||
</h4>
|
||||
<div
|
||||
class="text"
|
||||
v-html="itemNotes"
|
||||
></div>
|
||||
<div v-html="itemNotes"></div>
|
||||
<slot
|
||||
name="additionalInfo"
|
||||
:item="item"
|
||||
@@ -100,14 +97,14 @@
|
||||
>{{ item.value }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-else class="d-flex align-items-middle">
|
||||
<span
|
||||
class="svg-icon inline icon-32"
|
||||
class="svg-icon inline icon-32 ml-auto my-auto"
|
||||
aria-hidden="true"
|
||||
v-html="icons[getPriceClass()]"
|
||||
></span>
|
||||
<span
|
||||
class="cost"
|
||||
class="cost mr-auto my-auto"
|
||||
:class="getPriceClass()"
|
||||
>{{ item.value }}</span>
|
||||
</div>
|
||||
@@ -122,6 +119,9 @@
|
||||
<div v-if="attemptingToPurchaseMoreGemsThanAreLeft">
|
||||
{{ $t('notEnoughGemsToBuy') }}
|
||||
</div>
|
||||
<div v-if="nonSubscriberHourglasses" class="hourglass-nonsub mt-3">
|
||||
{{ $t('mysticHourglassNeededNoSub') }}
|
||||
</div>
|
||||
<button
|
||||
v-if="getPriceClass() === 'gems'
|
||||
&& !enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)"
|
||||
@@ -130,6 +130,13 @@
|
||||
>
|
||||
{{ $t('purchaseGems') }}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="nonSubscriberHourglasses"
|
||||
class="btn btn-primary"
|
||||
@click="viewSubscriptions(item)"
|
||||
>
|
||||
{{ $t('viewSubscriptions') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-primary"
|
||||
@@ -167,12 +174,11 @@
|
||||
</div>
|
||||
<div
|
||||
slot="modal-footer"
|
||||
class="clearfix"
|
||||
class="d-flex"
|
||||
>
|
||||
<span class="balance float-left">{{ $t('yourBalance') }}</span>
|
||||
<span class="balance mr-auto">{{ $t('yourBalance') }}</span>
|
||||
<balanceInfo
|
||||
class="float-right"
|
||||
:with-hourglass="getPriceClass() === 'hourglasses'"
|
||||
class="ml-auto"
|
||||
:currency-needed="getPriceClass()"
|
||||
:amount-needed="item.value"
|
||||
/>
|
||||
@@ -187,6 +193,10 @@
|
||||
#buy-modal {
|
||||
@include centeredModal();
|
||||
|
||||
.modal-body {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
width: 330px;
|
||||
}
|
||||
@@ -274,6 +284,7 @@
|
||||
button.btn.btn-primary {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
min-width: 6rem;
|
||||
}
|
||||
|
||||
.balance {
|
||||
@@ -360,6 +371,21 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.hourglass-nonsub {
|
||||
color: $yellow-5;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import keys from 'lodash/keys';
|
||||
import reduce from 'lodash/reduce';
|
||||
@@ -488,6 +514,9 @@ export default {
|
||||
nextFreeRebirth () {
|
||||
return 45 - moment().diff(moment(this.user.flags.lastFreeRebirth), 'days');
|
||||
},
|
||||
nonSubscriberHourglasses () {
|
||||
return (!this.user.purchased.plan.customerId && !this.user.purchased.plan.consecutive.trinkets && this.getPriceClass() === 'hourglasses');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
item: function itemChanged () {
|
||||
@@ -612,6 +641,25 @@ export default {
|
||||
|
||||
return {};
|
||||
},
|
||||
viewSubscriptions (item) {
|
||||
if (item.purchaseType === 'backgrounds') {
|
||||
this.$root.$emit('bv::hide::modal', 'avatar-modal');
|
||||
let removeIndex = this.$store.state.modalStack
|
||||
.map(modal => modal.modalId)
|
||||
.indexOf('avatar-modal');
|
||||
if (removeIndex >= 0) {
|
||||
this.$store.state.modalStack.splice(removeIndex, 1);
|
||||
}
|
||||
removeIndex = this.$store.state.modalStack
|
||||
.map(modal => modal.prev)
|
||||
.indexOf('avatar-modal');
|
||||
if (removeIndex >= 0) {
|
||||
delete this.$store.state.modalStack[removeIndex].prev;
|
||||
}
|
||||
}
|
||||
this.$router.push('/user/settings/subscription');
|
||||
this.hideDialog();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -160,7 +160,11 @@
|
||||
}
|
||||
|
||||
&.gold {
|
||||
color: $yellow-10
|
||||
color: $yellow-10;
|
||||
}
|
||||
|
||||
&.hourglasses {
|
||||
color: $hourglass-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -390,9 +390,9 @@ export default {
|
||||
// force update for now until we restructure the data
|
||||
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
|
||||
|
||||
const normalGroups = _filter(apiCategories, c => c.identifier === 'mounts' || c.identifier === 'pets' || c.identifier === 'quests');
|
||||
const normalGroups = _filter(apiCategories, c => c.identifier === 'mounts' || c.identifier === 'pets' || c.identifier === 'quests' || c.identifier === 'backgrounds');
|
||||
|
||||
const setGroups = _filter(apiCategories, c => c.identifier !== 'mounts' && c.identifier !== 'pets' && c.identifier !== 'quests');
|
||||
const setGroups = _filter(apiCategories, c => c.identifier !== 'mounts' && c.identifier !== 'pets' && c.identifier !== 'quests' && c.identifier !== 'backgrounds');
|
||||
|
||||
const setCategory = {
|
||||
identifier: 'sets',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="task-modal"
|
||||
:no-close-on-esc="showTagsSelect"
|
||||
:no-close-on-backdrop="showTagsSelect"
|
||||
:no-close-on-esc="true"
|
||||
:no-close-on-backdrop="true"
|
||||
size="sm"
|
||||
@hidden="onClose()"
|
||||
@show="handleOpen()"
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { storiesOf } from '@storybook/vue';
|
||||
import { withKnobs, number } from '@storybook/addon-knobs';
|
||||
|
||||
import CountBadge from './countBadge.vue';
|
||||
|
||||
storiesOf('Count Badge', module)
|
||||
const stories = storiesOf('Count Badge', module);
|
||||
|
||||
stories.addDecorator(withKnobs);
|
||||
|
||||
stories
|
||||
.add('simple', () => ({
|
||||
components: { CountBadge },
|
||||
template: `
|
||||
@@ -19,9 +24,9 @@ storiesOf('Count Badge', module)
|
||||
<count-badge :count="count" :show="true"></count-badge>
|
||||
</div>
|
||||
`,
|
||||
data () {
|
||||
return {
|
||||
count: 3,
|
||||
};
|
||||
props: {
|
||||
count: {
|
||||
default: number('Count', 3),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { storiesOf } from '@storybook/vue';
|
||||
import { text, withKnobs } from '@storybook/addon-knobs';
|
||||
|
||||
const stories = storiesOf('Textare', module);
|
||||
|
||||
stories.addDecorator(withKnobs);
|
||||
|
||||
stories
|
||||
.add('states', () => ({
|
||||
components: { },
|
||||
template: `
|
||||
<div style="position: absolute; margin: 20px">
|
||||
<textarea autofocus ref="area">Normal {{text}}</textarea> <button @click="$refs.area.focus()">Focus</button>
|
||||
<br />
|
||||
|
||||
<textarea disabled>Disabled {{text}}</textarea><br />
|
||||
|
||||
<textarea readonly>Readonly {{text}}</textarea> <br />
|
||||
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
text: {
|
||||
default: text('Area Message', 'example text'),
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -2,14 +2,14 @@
|
||||
<div class="popover-box">
|
||||
<div
|
||||
:id="containerId"
|
||||
class="clearfix"
|
||||
class="clearfix toggle-switch-outer"
|
||||
>
|
||||
<div
|
||||
v-if="label"
|
||||
class="float-left toggle-switch-description"
|
||||
:class="hoverText ? 'hasPopOver' : ''"
|
||||
>
|
||||
{{ label }}
|
||||
<span>{{ label }}</span>
|
||||
</div>
|
||||
<div class="toggle-switch float-left">
|
||||
<input
|
||||
@@ -53,9 +53,7 @@
|
||||
}
|
||||
|
||||
.toggle-switch-description {
|
||||
height: 20px;
|
||||
|
||||
&.hasPopOver {
|
||||
&.hasPopOver span {
|
||||
border-bottom: 1px dashed $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="displayName"
|
||||
v-b-tooltip.hover.top="tierTitle"
|
||||
class="user-label"
|
||||
:class="levelStyle()"
|
||||
>
|
||||
{{ displayName }}
|
||||
<div
|
||||
class="svg-icon"
|
||||
v-html="tierIcon()"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.user-label.no-tier {
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.user-label {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
display: inline-block;
|
||||
margin-left: .5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import achievementsLib from '@/../../common/script/libs/achievements';
|
||||
|
||||
import styleHelper from '@/mixins/styleHelper';
|
||||
|
||||
import tier1 from '@/assets/svg/tier-1.svg';
|
||||
import tier2 from '@/assets/svg/tier-2.svg';
|
||||
import tier3 from '@/assets/svg/tier-3.svg';
|
||||
import tier4 from '@/assets/svg/tier-4.svg';
|
||||
import tier5 from '@/assets/svg/tier-5.svg';
|
||||
import tier6 from '@/assets/svg/tier-6.svg';
|
||||
import tier7 from '@/assets/svg/tier-7.svg';
|
||||
import tier8 from '@/assets/svg/tier-mod.svg';
|
||||
import tier9 from '@/assets/svg/tier-staff.svg';
|
||||
import tierNPC from '@/assets/svg/tier-npc.svg';
|
||||
|
||||
export default {
|
||||
mixins: [styleHelper],
|
||||
props: ['user', 'name', 'backer', 'contributor', 'hideTooltip'],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
tier1,
|
||||
tier2,
|
||||
tier3,
|
||||
tier4,
|
||||
tier5,
|
||||
tier6,
|
||||
tier7,
|
||||
tier8,
|
||||
tier9,
|
||||
tierNPC,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
displayName () {
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.user && this.user.profile) {
|
||||
return this.user.profile.name;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
level () {
|
||||
if (this.contributor) {
|
||||
return this.contributor.level;
|
||||
} if (this.user && this.user.contributor) {
|
||||
return this.user.contributor.level;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
isNPC () {
|
||||
if (this.backer) {
|
||||
return this.backer.level;
|
||||
} if (this.user && this.user.backer) {
|
||||
return this.user.backer.level;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
tierIcon () {
|
||||
if (this.isNPC) {
|
||||
return this.icons.tierNPC;
|
||||
}
|
||||
return this.icons[`tier${this.level}`];
|
||||
},
|
||||
tierTitle () {
|
||||
return this.hideTooltip ? '' : achievementsLib.getContribText(this.contributor, this.isNPC) || '';
|
||||
},
|
||||
levelStyle () {
|
||||
return this.userLevelStyleFromLevel(this.level, this.isNPC);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,698 +0,0 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="inbox-modal"
|
||||
title
|
||||
:hide-footer="true"
|
||||
size="lg"
|
||||
@shown="onModalShown"
|
||||
@hide="onModalHide"
|
||||
>
|
||||
<div
|
||||
slot="modal-header"
|
||||
class="header-wrap container align-items-center"
|
||||
>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-2">
|
||||
<div
|
||||
class="svg-icon envelope"
|
||||
v-html="icons.messageIcon"
|
||||
></div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h2
|
||||
v-once
|
||||
class="text-center"
|
||||
>
|
||||
{{ $t('messages') }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 offset-3">
|
||||
<toggle-switch
|
||||
class="float-right"
|
||||
:label="optTextSet.switchDescription"
|
||||
:checked="!user.inbox.optOut"
|
||||
:hover-text="optTextSet.popoverText"
|
||||
@change="toggleOpt()"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<div class="close">
|
||||
<span
|
||||
class="svg-icon inline icon-10"
|
||||
aria-hidden="true"
|
||||
@click="close()"
|
||||
v-html="icons.svgClose"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4 sidebar">
|
||||
<div class="search-section">
|
||||
<b-form-input
|
||||
v-model="search"
|
||||
:placeholder="$t('search')"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="filtersConversations.length === 0"
|
||||
class="empty-messages text-center"
|
||||
>
|
||||
<div
|
||||
class="svg-icon envelope"
|
||||
v-html="icons.messageIcon"
|
||||
></div>
|
||||
<h4 v-once>
|
||||
{{ $t('emptyMessagesLine1') }}
|
||||
</h4>
|
||||
<p v-if="!user.flags.chatRevoked">
|
||||
{{ $t('emptyMessagesLine2') }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="filtersConversations.length > 0"
|
||||
class="conversations"
|
||||
>
|
||||
<div
|
||||
v-for="conversation in filtersConversations"
|
||||
:key="conversation.key"
|
||||
class="conversation"
|
||||
:class="{active: selectedConversation.key === conversation.key}"
|
||||
@click="selectConversation(conversation.key)"
|
||||
>
|
||||
<div>
|
||||
<h3 :class="userLevelStyle(conversation)">
|
||||
{{ conversation.name }}
|
||||
<div
|
||||
class="svg-icon"
|
||||
v-html="tierIcon(conversation)"
|
||||
></div>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="time">
|
||||
<span
|
||||
v-if="conversation.username"
|
||||
class="mr-1"
|
||||
>@{{ conversation.username }} •</span>
|
||||
<span v-if="conversation.date">{{ conversation.date | timeAgo }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="messagePreview"
|
||||
>
|
||||
{{ conversation.lastMessageText
|
||||
? removeTags(parseMarkdown(conversation.lastMessageText)) : '' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8 messages d-flex flex-column justify-content-between">
|
||||
<div
|
||||
v-if="!selectedConversation.key"
|
||||
class="empty-messages text-center"
|
||||
>
|
||||
<div
|
||||
class="svg-icon envelope"
|
||||
v-html="icons.messageIcon"
|
||||
></div>
|
||||
<h4>{{ placeholderTexts.title }}</h4>
|
||||
<p v-html="placeholderTexts.description"></p>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedConversation && selectedConversationMessages.length === 0"
|
||||
class="empty-messages text-center"
|
||||
>
|
||||
<p>{{ $t('beginningOfConversation', {userName: selectedConversation.name}) }}</p>
|
||||
</div>
|
||||
<chat-messages
|
||||
v-if="selectedConversation && selectedConversationMessages.length > 0"
|
||||
ref="chatscroll"
|
||||
class="message-scroll"
|
||||
:chat="selectedConversationMessages"
|
||||
:inbox="true"
|
||||
:can-load-more="canLoadMore"
|
||||
:is-loading="messagesLoading"
|
||||
@message-removed="messageRemoved"
|
||||
@triggerLoad="infiniteScrollTrigger"
|
||||
/>
|
||||
<div
|
||||
v-if="user.inbox.optOut && selectedConversation.key"
|
||||
class="pm-disabled-caption text-center"
|
||||
>
|
||||
<h4>{{ $t('PMDisabledCaptionTitle') }}</h4>
|
||||
<p>{{ $t('PMDisabledCaptionText') }}</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedConversation.key && !user.flags.chatRevoked"
|
||||
class="new-message-row"
|
||||
>
|
||||
<textarea
|
||||
v-model="newMessage"
|
||||
maxlength="3000"
|
||||
@keyup.ctrl.enter="sendPrivateMessage()"
|
||||
></textarea>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="sendPrivateMessage()"
|
||||
>
|
||||
{{ $t('send') }}
|
||||
</button>
|
||||
<div class="row">
|
||||
<span class="ml-3">{{ currentLength }} / 3000</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#inbox-modal .modal-body {
|
||||
padding-top: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.header-wrap {
|
||||
padding: 0.5em;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0rem;
|
||||
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
display: inline-block;
|
||||
margin-left: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.envelope {
|
||||
color: $gray-400 !important;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: $gray-700;
|
||||
min-height: 540px;
|
||||
padding: 0;
|
||||
|
||||
.search-section {
|
||||
padding: 1em;
|
||||
box-shadow: 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||
}
|
||||
}
|
||||
|
||||
.messages {
|
||||
position: relative;
|
||||
padding-left: 0;
|
||||
padding-bottom: 6em;
|
||||
height: 540px;
|
||||
}
|
||||
|
||||
.message-scroll {
|
||||
max-height: 500px;
|
||||
overflow-x: scroll;
|
||||
|
||||
@media (min-width: 992px) {
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
.to-form input {
|
||||
width: 60%;
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.empty-messages {
|
||||
margin-top: 10em;
|
||||
color: $gray-400;
|
||||
padding: 1em;
|
||||
|
||||
h4 {
|
||||
color: $gray-400;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.envelope {
|
||||
width: 30px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.pm-disabled-caption {
|
||||
|
||||
padding-top: 1em;
|
||||
background-color: $gray-700;
|
||||
z-index: 2;
|
||||
|
||||
h4, p {
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.new-message-row {
|
||||
background-color: $gray-700;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 88px;
|
||||
width: 100%;
|
||||
padding: 1em;
|
||||
|
||||
textarea {
|
||||
height: 80%;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
button {
|
||||
vertical-align: bottom;
|
||||
display: inline-block;
|
||||
box-shadow: none;
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.conversations {
|
||||
max-height: 400px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.conversation {
|
||||
padding: 1.5em;
|
||||
background: $white;
|
||||
}
|
||||
|
||||
.conversation.active {
|
||||
border: 1px solid $purple-400;
|
||||
}
|
||||
|
||||
.conversation:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 12px;
|
||||
color: $gray-200;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.messagePreview {
|
||||
display: block;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import moment from 'moment';
|
||||
import filter from 'lodash/filter';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import habiticaMarkdown from 'habitica-markdown';
|
||||
import axios from 'axios';
|
||||
import { mapState } from '@/libs/store';
|
||||
import styleHelper from '@/mixins/styleHelper';
|
||||
import toggleSwitch from '@/components/ui/toggleSwitch';
|
||||
|
||||
import chatMessages from '../chat/chatMessages';
|
||||
import messageIcon from '@/assets/svg/message.svg';
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
import tier1 from '@/assets/svg/tier-1.svg';
|
||||
import tier2 from '@/assets/svg/tier-2.svg';
|
||||
import tier3 from '@/assets/svg/tier-3.svg';
|
||||
import tier4 from '@/assets/svg/tier-4.svg';
|
||||
import tier5 from '@/assets/svg/tier-5.svg';
|
||||
import tier6 from '@/assets/svg/tier-6.svg';
|
||||
import tier7 from '@/assets/svg/tier-7.svg';
|
||||
import tier8 from '@/assets/svg/tier-mod.svg';
|
||||
import tier9 from '@/assets/svg/tier-staff.svg';
|
||||
import tierNPC from '@/assets/svg/tier-npc.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
chatMessages,
|
||||
toggleSwitch,
|
||||
},
|
||||
filters: {
|
||||
timeAgo (value) {
|
||||
return moment(new Date(value)).fromNow();
|
||||
},
|
||||
},
|
||||
mixins: [styleHelper],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
messageIcon,
|
||||
svgClose,
|
||||
tier1,
|
||||
tier2,
|
||||
tier3,
|
||||
tier4,
|
||||
tier5,
|
||||
tier6,
|
||||
tier7,
|
||||
tier8,
|
||||
tier9,
|
||||
tierNPC,
|
||||
}),
|
||||
displayCreate: true,
|
||||
selectedConversation: {},
|
||||
search: '',
|
||||
newMessage: '',
|
||||
showPopover: false,
|
||||
messages: [],
|
||||
messagesByConversation: {}, // cache {uuid: []}
|
||||
loadedConversations: [],
|
||||
loaded: false,
|
||||
messagesLoading: false,
|
||||
initiatedConversation: null,
|
||||
updateConversionsCounter: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
canLoadMore () {
|
||||
return this.selectedConversation && this.selectedConversation.canLoadMore;
|
||||
},
|
||||
conversations () {
|
||||
const inboxGroup = groupBy(this.loadedConversations, 'uuid');
|
||||
|
||||
// Add placeholder for new conversations
|
||||
if (this.initiatedConversation && this.initiatedConversation.uuid) {
|
||||
inboxGroup[this.initiatedConversation.uuid] = [{
|
||||
uuid: this.initiatedConversation.uuid,
|
||||
user: this.initiatedConversation.user,
|
||||
username: this.initiatedConversation.username,
|
||||
contributor: this.initiatedConversation.contributor,
|
||||
id: '',
|
||||
text: '',
|
||||
timestamp: new Date(),
|
||||
}];
|
||||
}
|
||||
// Create conversation objects
|
||||
const convos = [];
|
||||
for (const key of Object.keys(inboxGroup)) {
|
||||
const recentMessage = inboxGroup[key][0];
|
||||
|
||||
const convoModel = {
|
||||
key: recentMessage.uuid,
|
||||
// Handles case where from user sent
|
||||
// the only message or the to user sent the only message
|
||||
name: recentMessage.user,
|
||||
username: !recentMessage.text ? recentMessage.username : recentMessage.toUserName,
|
||||
date: recentMessage.timestamp,
|
||||
lastMessageText: recentMessage.text,
|
||||
canLoadMore: true,
|
||||
page: 0,
|
||||
};
|
||||
|
||||
convos.push(convoModel);
|
||||
}
|
||||
|
||||
return convos;
|
||||
},
|
||||
// Separate from selectedConversation which
|
||||
// is not computed so messages don't update automatically
|
||||
selectedConversationMessages () {
|
||||
// Vue-subscribe to changes
|
||||
const subScribeToUpdate = this.messagesLoading || this.updateConversionsCounter > -1;
|
||||
|
||||
const selectedConversationKey = this.selectedConversation.key;
|
||||
const selectedConversation = this.messagesByConversation[selectedConversationKey];
|
||||
this.messages = selectedConversation || []; // eslint-disable-line vue/no-side-effects-in-computed-properties, max-len
|
||||
|
||||
const ordered = orderBy(this.messages, [m => m.timestamp], ['asc']);
|
||||
|
||||
if (subScribeToUpdate) {
|
||||
return ordered;
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
filtersConversations () {
|
||||
// Vue-subscribe to changes
|
||||
const subScribeToUpdate = this.updateConversionsCounter > -1;
|
||||
|
||||
const filtered = subScribeToUpdate && !this.search
|
||||
? this.conversations
|
||||
: filter(
|
||||
this.conversations,
|
||||
conversation => conversation.name.toLowerCase().indexOf(this.search.toLowerCase()) !== -1,
|
||||
);
|
||||
|
||||
const ordered = orderBy(filtered, [o => moment(o.date).toDate()], ['desc']);
|
||||
|
||||
return ordered;
|
||||
},
|
||||
currentLength () {
|
||||
return this.newMessage.length;
|
||||
},
|
||||
placeholderTexts () {
|
||||
if (this.user.flags.chatRevoked) {
|
||||
return {
|
||||
title: this.$t('PMPlaceholderTitleRevoked'),
|
||||
description: this.$t('chatPrivilegesRevoked'),
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: this.$t('PMPlaceholderTitle'),
|
||||
description: this.$t('PMPlaceholderDescription'),
|
||||
};
|
||||
},
|
||||
optTextSet () {
|
||||
if (!this.user.inbox.optOut) {
|
||||
return {
|
||||
switchDescription: this.$t('PMReceive'),
|
||||
popoverText: this.$t('PMEnabledOptPopoverText'),
|
||||
};
|
||||
}
|
||||
return {
|
||||
switchDescription: this.$t('PMReceive'),
|
||||
popoverText: this.$t('PMDisabledOptPopoverText'),
|
||||
};
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica::new-inbox-message', data => {
|
||||
this.$root.$emit('bv::show::modal', 'inbox-modal');
|
||||
|
||||
// Wait for messages to be loaded
|
||||
const unwatchLoaded = this.$watch('loaded', loaded => {
|
||||
if (!loaded) return;
|
||||
|
||||
const conversation = this.conversations.find(convo => convo.key === data.userIdToMessage);
|
||||
if (loaded) setImmediate(() => unwatchLoaded());
|
||||
|
||||
if (conversation) {
|
||||
this.selectConversation(data.userIdToMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
this.initiatedConversation = {
|
||||
uuid: data.userIdToMessage,
|
||||
user: data.displayName,
|
||||
username: data.username,
|
||||
backer: data.backer,
|
||||
contributor: data.contributor,
|
||||
};
|
||||
|
||||
this.selectConversation(data.userIdToMessage);
|
||||
}, { immediate: true });
|
||||
});
|
||||
},
|
||||
destroyed () {
|
||||
this.$root.$off('habitica::new-inbox-message');
|
||||
},
|
||||
methods: {
|
||||
async onModalShown () {
|
||||
this.loaded = false;
|
||||
|
||||
const conversationRes = await axios.get('/api/v4/inbox/conversations');
|
||||
this.loadedConversations = conversationRes.data.data;
|
||||
|
||||
this.loaded = true;
|
||||
},
|
||||
onModalHide () {
|
||||
// reset everything
|
||||
this.loadedConversations = [];
|
||||
this.loaded = false;
|
||||
this.initiatedConversation = null;
|
||||
this.messagesByConversation = {};
|
||||
this.selectedConversation = {};
|
||||
},
|
||||
messageRemoved (message) {
|
||||
const messages = this.messagesByConversation[this.selectedConversation.key];
|
||||
|
||||
const messageIndex = messages.findIndex(msg => msg.id === message.id);
|
||||
if (messageIndex !== -1) messages.splice(messageIndex, 1);
|
||||
if (this.selectedConversationMessages.length === 0) {
|
||||
this.initiatedConversation = {
|
||||
uuid: this.selectedConversation.key,
|
||||
user: this.selectedConversation.name,
|
||||
username: this.selectedConversation.username,
|
||||
backer: this.selectedConversation.backer,
|
||||
contributor: this.selectedConversation.contributor,
|
||||
};
|
||||
}
|
||||
},
|
||||
toggleClick () {
|
||||
this.displayCreate = !this.displayCreate;
|
||||
},
|
||||
toggleOpt () {
|
||||
this.$store.dispatch('user:togglePrivateMessagesOpt');
|
||||
},
|
||||
async selectConversation (key) {
|
||||
const convoFound = this.conversations.find(conversation => conversation.key === key);
|
||||
|
||||
this.selectedConversation = convoFound || {};
|
||||
|
||||
if (!this.messagesByConversation[this.selectedConversation.key]) {
|
||||
await this.loadMessages();
|
||||
}
|
||||
|
||||
Vue.nextTick(() => {
|
||||
if (!this.$refs.chatscroll) return;
|
||||
const chatscroll = this.$refs.chatscroll.$el;
|
||||
chatscroll.scrollTop = chatscroll.scrollHeight;
|
||||
});
|
||||
},
|
||||
sendPrivateMessage () {
|
||||
if (!this.newMessage) return;
|
||||
|
||||
const messages = this.messagesByConversation[this.selectedConversation.key];
|
||||
|
||||
messages.push({
|
||||
sent: true,
|
||||
text: this.newMessage,
|
||||
timestamp: new Date(),
|
||||
toUser: this.selectedConversation.name,
|
||||
toUserName: this.selectedConversation.username,
|
||||
toUserContributor: this.selectedConversation.contributor,
|
||||
toUserBacker: this.selectedConversation.backer,
|
||||
toUUID: this.selectedConversation.uuid,
|
||||
|
||||
id: '-1', // will be updated once the result is back
|
||||
likes: {},
|
||||
ownerId: this.user._id,
|
||||
uuid: this.user._id,
|
||||
fromUUID: this.user._id,
|
||||
user: this.user.profile.name,
|
||||
username: this.user.auth.local.username,
|
||||
contributor: this.user.contributor,
|
||||
backer: this.user.backer,
|
||||
});
|
||||
|
||||
// Remove the placeholder message
|
||||
if (
|
||||
this.initiatedConversation
|
||||
&& this.initiatedConversation.uuid === this.selectedConversation.key
|
||||
) {
|
||||
this.loadedConversations.unshift(this.initiatedConversation);
|
||||
this.initiatedConversation = null;
|
||||
}
|
||||
|
||||
this.selectedConversation.lastMessageText = this.newMessage;
|
||||
this.selectedConversation.date = new Date();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
if (!this.$refs.chatscroll) return;
|
||||
const chatscroll = this.$refs.chatscroll.$el;
|
||||
chatscroll.scrollTop = chatscroll.scrollHeight;
|
||||
});
|
||||
|
||||
this.$store.dispatch('members:sendPrivateMessage', {
|
||||
toUserId: this.selectedConversation.key,
|
||||
message: this.newMessage,
|
||||
}).then(response => {
|
||||
const newMessage = response.data.data.message;
|
||||
const messageToReset = messages[messages.length - 1];
|
||||
messageToReset.id = newMessage.id; // just set the id, all other infos already set
|
||||
Object.assign(messages[messages.length - 1], messageToReset);
|
||||
this.updateConversionsCounter += 1;
|
||||
});
|
||||
|
||||
this.newMessage = '';
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'inbox-modal');
|
||||
},
|
||||
tierIcon (message) {
|
||||
const isNPC = Boolean(message.backer && message.backer.npc);
|
||||
if (isNPC) {
|
||||
return this.icons.tierNPC;
|
||||
}
|
||||
if (!message.contributor) return null;
|
||||
return this.icons[`tier${message.contributor.level}`];
|
||||
},
|
||||
removeTags (html) {
|
||||
const tmp = document.createElement('DIV');
|
||||
tmp.innerHTML = html;
|
||||
return tmp.textContent || tmp.innerText || '';
|
||||
},
|
||||
parseMarkdown (text) {
|
||||
if (!text) return null;
|
||||
return habiticaMarkdown.render(String(text));
|
||||
},
|
||||
infiniteScrollTrigger () {
|
||||
// show loading and wait until the loadMore debounced
|
||||
// or else it would trigger on every scrolling-pixel (while not loading)
|
||||
if (this.canLoadMore) {
|
||||
this.messagesLoading = true;
|
||||
}
|
||||
|
||||
return this.loadMore();
|
||||
},
|
||||
loadMore () {
|
||||
this.selectedConversation.page += 1;
|
||||
return this.loadMessages();
|
||||
},
|
||||
async loadMessages () {
|
||||
this.messagesLoading = true;
|
||||
|
||||
const requestUrl = `/api/v4/inbox/paged-messages?conversation=${this.selectedConversation.key}&page=${this.selectedConversation.page}`;
|
||||
const res = await axios.get(requestUrl);
|
||||
const loadedMessages = res.data.data;
|
||||
|
||||
this.messagesByConversation[this.selectedConversation.key] = this.messagesByConversation[this.selectedConversation.key] || []; // eslint-disable-line max-len
|
||||
const loadedMessagesToAdd = loadedMessages
|
||||
.filter(m => this.messagesByConversation[this.selectedConversation.key].findIndex(mI => mI.id === m.id) === -1); // eslint-disable-line max-len
|
||||
this.messagesByConversation[this.selectedConversation.key].push(...loadedMessagesToAdd);
|
||||
|
||||
// only show the load more Button if the max count was returned
|
||||
this.selectedConversation.canLoadMore = loadedMessages.length === 10;
|
||||
this.messagesLoading = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -859,13 +859,12 @@ export default {
|
||||
window.history.replaceState(null, null, '');
|
||||
},
|
||||
sendMessage () {
|
||||
this.$root.$emit('habitica::new-inbox-message', {
|
||||
userIdToMessage: this.user._id,
|
||||
displayName: this.user.profile.name,
|
||||
username: this.user.auth.local.username,
|
||||
backer: this.user.backer,
|
||||
contributor: this.user.contributor,
|
||||
this.$store.dispatch('user:newPrivateMessageTo', {
|
||||
member: this.user,
|
||||
});
|
||||
|
||||
this.$router.push('/private-messages');
|
||||
this.$root.$emit('bv::hide::modal', 'profile');
|
||||
},
|
||||
getProgressDisplay () {
|
||||
// let currentLoginDay = Content.loginIncentives[this.user.loginIncentives];
|
||||
|
||||