Compare commits

...

119 Commits

Author SHA1 Message Date
Sabe Jones
30036963b1 4.99.0 2019-06-04 16:48:53 -05:00
Sabe Jones
e1fe48bee4 chore(sprites): compile 2019-06-04 16:48:16 -05:00
Sabe Jones
77b19ffe97 feat(content): Armoire items and Backgrounds June 2019 2019-06-04 16:48:05 -05:00
Sabe Jones
e4b2ef6599 4.98.1 2019-06-03 15:22:30 -05:00
Sabe Jones
a26c2bce23 chore(date): Bailey and disable potions 2019-06-03 15:22:21 -05:00
Sabe Jones
783338f1f0 fix(strings): misformatted variables in pt_BR 2019-05-31 14:56:26 -05:00
Sabe Jones
4132a39b90 4.98.0 2019-05-30 15:14:04 -05:00
Sabe Jones
36cc229dd2 Merge branch 'develop' into release 2019-05-30 15:13:57 -05:00
Sabe Jones
ef0c11c2bd chore(sprites): compile 2019-05-30 15:13:35 -05:00
Sabe Jones
3a8312832c feat(content): new freebie glasses
Fixes #11171
2019-05-30 15:13:23 -05:00
Sabe Jones
df17753bb6 Merge branch 'release' into develop 2019-05-28 15:48:51 -05:00
Sabe Jones
c9b3c646eb 4.97.0 2019-05-28 15:48:07 -05:00
Sabe Jones
cd67877ef3 chore(sprites): compile 2019-05-28 15:45:16 -05:00
Sabe Jones
251e1d45af feat(content): May 2019 subscriber set 2019-05-28 15:45:04 -05:00
Matteo Pagliazzi
1a97d69edd Merge pull request #10865 from ianoxley/navbar-a11y-alt-text
Add text alternatives for navbar items
2019-05-26 11:59:04 +02:00
Matteo Pagliazzi
7baa7427a0 Merge pull request #11178 from Alys/mute-ban-block-notification-messages
adjust error messages for muted and banned users, and system flagging error
2019-05-26 11:49:24 +02:00
Matteo Pagliazzi
270078a030 Merge pull request #11170 from HabitRPG/Yutsuten-party-chat-translations
Party Chat Translations (Continuation of #10019)
2019-05-26 11:45:12 +02:00
Alys
bb2768071d change "all caps" to "all capital letters" for clarity
I found this question in the Help guild:
'I'm deleting my account and aren't able to find the "cap" to type in
"delete" in order to finish deleting process. Any ideas were that
"cap" is?'
2019-05-26 16:43:42 +10:00
Alys
ec75de5a90 add swear words - TRIGGER / CONTENT WARNING: assault, slurs, swearwords, etc 2019-05-26 16:25:33 +10:00
Alys
b9b944ba29 replace similar messages about chat privileges removed with one generic one 2019-05-26 07:59:23 +10:00
Alys
fa9553b371 removed mention of Transifex and added link to new Weblate site 2019-05-25 19:57:29 +10:00
Mateus Etto
8d5cbbe9be Docker setup for development (#11165)
* Basic docker setup for development

* Add missing environment variable to client

* Install gulp-cli into docker image for tests
2019-05-24 14:07:21 -05:00
Sabe Jones
3a339a4a09 Merge branch 'release' into develop 2019-05-23 13:41:14 -05:00
Sabe Jones
324b16b8cd 4.96.1 2019-05-23 13:41:00 -05:00
Sabe Jones
8c005aa05a fix(shops): remove outdated Featured Item
and Bailey
2019-05-23 13:40:06 -05:00
Sabe Jones
664cf5a47b Merge branch 'release' into develop 2019-05-21 15:29:40 -05:00
Sabe Jones
71926cff51 4.96.0 2019-05-21 15:29:22 -05:00
Matteo Pagliazzi
e971f49c85 fix(test): fix stripe error message output 2019-05-21 15:17:57 -05:00
Sabe Jones
01657f573d feat(quests): create Hatching Potion category 2019-05-21 14:48:44 -05:00
Sabe Jones
95613dcfb8 Merge branch 'sabrecat/potion-quests' into release 2019-05-21 14:28:46 -05:00
Matteo Pagliazzi
bcf304984d Task notes now disappear when they are deleted from the main task. Fix #11152 (#11167)
* Task notes now disappear when they are deleted from the main task.

* Html component changed back to Markdown. Markdown logic now accounts for if the value is an empty string.

* if-else statement to be sure that the markdown library doesn't create issues with empty strings.
2019-05-20 11:46:44 +02:00
Chris Pomerville
08d84ba691 hides item count in quest sidebar section for non-participants (#11183) 2019-05-20 11:33:47 +02:00
Matteo Pagliazzi
4007c26801 fix(test): fix stripe error message output 2019-05-20 11:31:20 +02:00
Alys
344c20fd99 improve error notification shown when a player is blocked
This improves the wording in the error notification that a player
sees when they've been blocked.

It also also changes markdown links to plain text because raw
markdown was being shown in some locations (for example when you saw
the error messages on the command line while using an API command).

These changes have been discussed with and approved by beffymaroo
and the other mods.
2019-05-18 21:38:38 +10:00
Alys
f5c2c39f6a adjust error messages for muted users and system flagging error 2019-05-18 17:12:24 +10:00
Sabe Jones
0379f341b9 4.95.1 2019-05-16 14:07:57 -05:00
Sabe Jones
8498ab9fe2 chore(news): Bailey 2019-05-16 14:07:39 -05:00
Jose Garay
bda3fb5f4d if-else statement to be sure that the markdown library doesn't create issues with empty strings. 2019-05-15 08:10:44 -07:00
Matteo Pagliazzi
eff8db0afd fix issue with language being undefined, refactoring auth middleware, new tests 2019-05-15 16:54:55 +02:00
Matteo Pagliazzi
8cce38ede1 Merge branch 'party-chat-translations' of https://github.com/Yutsuten/habitica into Yutsuten-party-chat-translations 2019-05-15 15:20:40 +02:00
Mateus Etto
ab0dae8df3 Translate messages only after serialization 2019-05-15 21:26:23 +09:00
Mateus Etto
0824af05b7 Remove unneeded parameter on method call 2019-05-15 19:45:42 +09:00
Mateus Etto
28bf024990 Move translateMessage to libs 2019-05-15 19:07:17 +09:00
Mateus Etto
8c59420d4e Fix quest cancel test 2019-05-15 17:28:05 +09:00
Mateus Etto
8a30ac0607 Add translation support for quest cancel 2019-05-15 16:55:37 +09:00
Mateus Etto
e1984762b5 Fix some tests 2019-05-15 16:55:27 +09:00
Mateus Etto
0360326f41 Change translateSystemMessages to be a class method 2019-05-15 15:46:32 +09:00
Mateus Etto
f3f215abea Fix typo on schema 2019-05-15 13:50:57 +09:00
Jose Garay
feb98a5ac7 Html component changed back to Markdown. Markdown logic now accounts for if the value is an empty string. 2019-05-14 19:06:22 -07:00
Sabe Jones
95de11cf7a Merge branch 'release' into develop 2019-05-14 16:35:29 -05:00
Sabe Jones
5bb336cedc 4.95.0 2019-05-14 16:34:29 -05:00
Sabe Jones
790614a9d4 chore(sprites): compile 2019-05-14 16:33:51 -05:00
Sabe Jones
977968df19 feat(content): Sunshine Potions 2019-05-14 16:33:42 -05:00
HydeHunter2
3df056105c Remove markdown elements from PM (#11150)
* Remove markdown elements from PM

* Replacing limit number of characters to limit number of lines

* Add ellipsis
2019-05-14 22:14:10 +02:00
Sabe Jones
0eb7e10e7f fix(sprites): rebuild including Wolf 2019-05-14 13:22:45 -05:00
Alys
0908fa2a48 change Load Tools icon that mods and staff use (#11169)
The crown icon makes it clearer to us that it's for the special
mod/staff tools. The pencil icon is too similar to normal
edit/compose icons and we were clicking it by mistake, with
accidental double-clicks resulting in users being briefly banned by
mistake since the ban icon is directly "under" the Load Tools icon.

I've checked with the other mods and they feel that the crown will
avoid that problem.
2019-05-14 14:04:28 +02:00
Sabe Jones
71904d106f chore(sprites): compile 2019-05-13 11:02:19 -05:00
Sabe Jones
56040eebaf feat(content): Magic Hatching Potion Quest 2019-05-13 11:02:07 -05:00
Jose Garay
2094a4d4b8 Task notes now disappear when they are deleted from the main task. 2019-05-11 23:47:24 -07:00
Sabe Jones
2048f3ef76 fix(string): correct stat reference 2019-05-10 07:23:20 -05:00
Sabe Jones
e9163a1bb2 fix(test): Feathered Friends date range 2019-05-09 14:53:55 -05:00
Sabe Jones
54fd910db2 fix(test): Feathered Friends date range 2019-05-09 14:53:38 -05:00
Sabe Jones
3c0cd7067a 4.94.1 2019-05-09 14:32:23 -05:00
Sabe Jones
5a473eb0e0 Merge branch 'develop' into release 2019-05-09 14:32:14 -05:00
Sabe Jones
e6fcdf62ef fix(sprites): dojo resize fix 2019-05-09 14:31:28 -05:00
Sabe Jones
09e748bc92 feat(content): reenable Feathered Friends bundle 2019-05-09 14:27:22 -05:00
Sabe Jones
90532b0763 Merge branch 'develop' into Yutsuten/party-chat-translations 2019-05-08 15:13:53 -05:00
Matteo Pagliazzi
9151690f86 Better group plan and subscription cancellation (#11132)
* wip: better group plan cancellation

* add cancelation confirm modal

* abstract confirm modal for subs

* abstract canceled modal for subs

* working code

* add missing files

* fix text and margins

* fix(cancel modal): share css and add close icon
2019-05-08 21:37:02 +02:00
Sabe Jones
c125ac4d93 fix(client): avoid TypeError in store state 2019-05-08 12:18:42 -05:00
Sabe Jones
aa61c1fe06 Merge branch 'release' into develop 2019-05-07 15:57:44 -05:00
Sabe Jones
c6a7ee3f56 4.94.0 2019-05-07 15:57:14 -05:00
Sabe Jones
411213f381 chore(sprites): compile 2019-05-07 15:57:02 -05:00
Sabe Jones
b2dabcaf98 feat(content): Armoire items and backgrounds 5/19 2019-05-07 15:56:51 -05:00
chen
2f3927fcaa column-background should have 100% width (#11142) 2019-05-03 15:57:00 +02:00
negue
f84562446d fix loading owned messages (#11147) 2019-05-03 15:53:35 +02:00
HydeHunter2
95c1893b0c Fix challenge update (#11148)
* Fix challenge update

Fix some problem in challenge update

* Fix test

* Move leader to noUpdate

* Move leader to noUpdate
2019-05-03 15:35:12 +02:00
Ian Oxley
6fba71ea2c Add :focus styles to match :hover styles
Add :focus styles to the .habitica-menu-dropdown.
These match the existing :hover styles.
2019-03-28 20:38:17 +00:00
Ian Oxley
5755bfc952 Merge branch 'develop' into navbar-a11y-alt-text 2019-03-28 20:19:04 +00:00
Ian Oxley
08c6e8298c Fix linting errors
Remove parentheses and add trailing comma.
2018-11-24 17:12:12 +00:00
Ian Oxley
5a30b0cf1f Add aria-label to Habitica logo 2018-11-23 22:44:31 +00:00
Ian Oxley
cf847cd1d8 Add aria-label to notifaction and user menus
Add aria-label to give a text equivalent for non-visual users.
2018-11-23 22:44:31 +00:00
Ian Oxley
5753d3e648 Improve a11y for the dropdown menu
Add role="button" to make the component report itself as a button.

Add tabindex so the menu toggle can receive keyboard focus.

Add keydown handlers for `<Enter>` and `<Space>` so the dropdown menu
toggle responds to keyboard input.

Set the aria-pressed attribute to true if the menu is open, or false if
it is closed.
2018-11-23 22:44:30 +00:00
Ian Oxley
4718e5e5ea Improve a11y for the sync links
Add `role="link"` so it shows up as a link in VoiceOver.

Add `tabindex="0" so it can receive keyboard focus, and a keyup handler
for the enter key so it will respond to `<Enter>` keypresses.

Add `aria-label="$t('sync')"` to add text for non-visual users.

Add aria-label to mobile sync icon link.
2018-11-23 22:44:06 +00:00
Ian Oxley
9a2dbace30 Add aria-label to Gems and Gold icons
Add `aria-label` attributes to the gems and gold icons in the menu.

Make the gems icon a link, with a href set to `#buy-gems`, which is the element
that contains the gems dialog.
2018-11-23 22:30:43 +00:00
Mateus Etto
ef07abfd28 Merge branch 'develop' into party-chat-translations
# Conflicts:
#	test/api/unit/models/group.test.js
#	website/server/controllers/api-v3/user/spells.js
#	website/server/models/chat.js
#	website/server/models/group.js
2018-10-08 18:44:02 +09:00
Mateus Etto
b28adc6b42 Merge branch 'develop' into party-chat-translations 2018-08-29 20:07:19 +09:00
Mateus Etto
c814eabb29 Merge branch 'develop' into party-chat-translations 2018-07-21 16:06:38 +09:00
Mateus Etto
4dc19a8c60 Merge branch 'develop' into party-chat-translations 2018-07-12 06:28:44 +09:00
Mateus Etto
89f6a9b07e Merge branch 'develop' into party-chat-translations
# Conflicts:
#	test/api/unit/models/group.test.js
2018-06-20 20:24:38 +09:00
Mateus Etto
a23926f34f Merge branch 'develop' into party-chat-translations 2018-05-19 10:21:54 +09:00
Mateus Etto
118198b594 Fixed tests that contains random data 2018-05-19 00:26:28 +09:00
Mateus Etto
97e80e2093 Implemented requested changes 2018-05-18 23:08:37 +09:00
Mateus Etto
2588befceb Merge branch 'develop' into party-chat-translations
# Conflicts:
#	website/server/models/group.js
2018-05-06 00:46:47 +09:00
Mateus Etto
f09274225a Update chat schema: add 'info' field 2018-04-25 21:28:02 +09:00
Mateus Etto
03b66abe70 Merge branch 'develop' into party-chat-translations
# Conflicts:
#	website/server/controllers/api-v3/quests.js
#	website/server/controllers/api-v3/tasks/groups.js
#	website/server/controllers/api-v3/user/spells.js
#	website/server/models/group.js
2018-04-25 21:15:49 +09:00
Mateus Etto
9d41fb0252 Add tests + some small adjusts 2018-03-25 00:01:20 +09:00
Mateus Etto
012fa2f8ef Improve readability of translateSystemMessages function 2018-03-18 12:09:03 +09:00
Mateus Etto
00d8d9d0cc Merge branch 'develop' into party-chat-translations 2018-03-17 00:08:20 +09:00
Mateus Etto
cc4772c75a Update test 2018-03-12 23:48:09 +09:00
Mateus Etto
fabaaa6d92 Merge branch 'develop' into party-chat-translations 2018-03-12 22:35:13 +09:00
Mateus Etto
854696728a Fix message when boss don't attack 2018-03-12 21:50:23 +09:00
Mateus Etto
314926cc06 Hard coded messages now using i18n strings 2018-03-12 21:45:41 +09:00
Mateus Etto
68fa834946 spellName -> spell; grammar fix 2018-03-12 21:08:20 +09:00
Mateus Etto
09440d5adf Merge branch 'develop' into party-chat-translations 2018-03-02 19:28:41 +09:00
Mateus Etto
1724bfc553 Merge branch 'develop' into party-chat-translations
# Conflicts:
#	website/server/models/group.js
2018-02-28 19:42:19 +09:00
Mateus Etto
8304f99ecb Added comment explaining the new info object 2018-02-26 20:00:47 +09:00
Mateus Etto
d2fcdf4493 Merge branch 'develop' into party-chat-translations
# Conflicts:
#	website/server/controllers/api-v3/user.js
2018-02-26 19:55:32 +09:00
Mateus Etto
649404ac6a Fix travis-ci error 2018-02-19 20:57:31 +09:00
Mateus Etto
774db2564f Moving some strings around 2018-02-19 20:17:03 +09:00
Mateus Etto
521a1e646d Missing message (user claim task) 2018-02-19 20:07:05 +09:00
Mateus Etto
2619ac37d9 Fix mistake on tavern_boss_rage_tired 2018-02-19 00:36:49 +09:00
Mateus Etto
2ce9b319a0 Target username instead of uuid 2018-02-19 00:24:30 +09:00
Mateus Etto
477c23dd67 Save username instead of uuid (no queries necessary anymore) 2018-02-19 00:18:29 +09:00
Mateus Etto
7af71d1457 Fix errors 2018-02-18 19:44:17 +09:00
Mateus Etto
3171550de2 Fix syntax problems found when running tests 2018-02-18 17:14:18 +09:00
Mateus Etto
6477801d3e Translation support for missing sendChat calls 2018-02-18 15:26:27 +09:00
Mateus Etto
14798ced82 Translation support for a lot of messages in party 2018-02-17 22:17:08 +09:00
Mateus Etto
34d37cefcc Boss damage messages translation support 2018-02-17 19:41:15 +09:00
Mateus Etto
bd21933cea Quest started translation support 2018-02-17 17:15:14 +09:00
268 changed files with 30802 additions and 28766 deletions

View File

@@ -1,18 +1,5 @@
FROM node:10
# Install global packages
RUN npm install -g gulp-cli mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN cp config.json.example config.json
RUN npm install
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["npm", "start"]
FROM node:10
WORKDIR /code
COPY package*.json /code/
RUN npm install
RUN npm install -g gulp-cli mocha

View File

@@ -1,14 +1,45 @@
version: "3"
services:
client:
build:
context: .
dockerfile: ./Dockerfile-Dev
command: ["npm", "run", "client:dev"]
depends_on:
- server
environment:
- NODE_ENV=development
- BASE_URL=http://server:3000
image: habitica
networks:
- habitica
ports:
- "8080:8080"
volumes:
- '.:/usr/src/habitrpg'
- .:/code
- /code/node_modules
server:
build:
context: .
dockerfile: ./Dockerfile-Dev
command: ["npm", "start"]
depends_on:
- mongo
environment:
- NODE_ENV=development
- NODE_DB_URI=mongodb://mongo/habitrpg
image: habitica
networks:
- habitica
ports:
- "3000:3000"
volumes:
- '.:/usr/src/habitrpg'
- .:/code
- /code/node_modules
mongo:
image: mongo:3.4
networks:
- habitica
ports:
- "27017:27017"
networks:
habitica:
driver: bridge

View File

@@ -16,7 +16,7 @@ const IMG_DIST_PATH = 'website/client/assets/images/sprites/';
const CSS_DIST_PATH = 'website/client/assets/css/sprites/';
function checkForSpecialTreatment (name) {
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame|^eyewear_special_\w+HalfMoon/;
return name.match(regex) || name === 'head_0';
}

View File

@@ -0,0 +1,62 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20190530_halfmoon_glasses';
import { v4 as uuid } from 'uuid';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {
'items.gear.owned.eyewear_special_blackHalfMoon': true,
'items.gear.owned.eyewear_special_blueHalfMoon': true,
'items.gear.owned.eyewear_special_greenHalfMoon': true,
'items.gear.owned.eyewear_special_pinkHalfMoon': true,
'items.gear.owned.eyewear_special_redHalfMoon': true,
'items.gear.owned.eyewear_special_whiteHalfMoon': true,
'items.gear.owned.eyewear_special_yellowHalfMoon': true,
};
set.migration = MIGRATION_NAME;
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$set: set}).exec();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2019-05-01')},
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -1,6 +1,6 @@
/* eslint-disable no-console */
const MIGRATION_NAME = 'mystery_items_201904';
const MYSTERY_ITEMS = ['armor_mystery_201904', 'head_mystery_201904'];
const MIGRATION_NAME = 'mystery_items_201905';
const MYSTERY_ITEMS = ['headAccessory_mystery_201905', 'back_mystery_201905'];
import { model as User } from '../../website/server/models/user';
import { model as UserNotification } from '../../website/server/models/userNotification';

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "habitica",
"version": "4.93.4",
"version": "4.99.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.93.4",
"version": "4.99.0",
"main": "./website/server/index.js",
"dependencies": {
"@google-cloud/trace-agent": "^3.6.0",

View File

@@ -16,7 +16,7 @@ describe('auth middleware', () => {
describe('auth with headers', () => {
it('allows to specify a list of user field that we do not want to load', (done) => {
const authWithHeaders = authWithHeadersFactory({
userFieldsToExclude: ['items', 'flags', 'auth.timestamps'],
userFieldsToExclude: ['items'],
});
req.headers['x-api-user'] = user._id;
@@ -27,11 +27,34 @@ describe('auth middleware', () => {
const userToJSON = res.locals.user.toJSON();
expect(userToJSON.items).to.not.exist;
expect(userToJSON.flags).to.not.exist;
expect(userToJSON.auth.timestamps).to.not.exist;
expect(userToJSON.auth).to.exist;
done();
});
});
it('makes sure some fields are always included', (done) => {
const authWithHeaders = authWithHeadersFactory({
userFieldsToExclude: [
'items', 'auth.timestamps',
'preferences', 'notifications', '_id', 'flags', 'auth', // these are always loaded
],
});
req.headers['x-api-user'] = user._id;
req.headers['x-api-key'] = user.apiToken;
authWithHeaders(req, res, (err) => {
if (err) return done(err);
const userToJSON = res.locals.user.toJSON();
expect(userToJSON.items).to.not.exist;
expect(userToJSON.auth.timestamps).to.exist;
expect(userToJSON.auth).to.exist;
expect(userToJSON.notifications).to.exist;
expect(userToJSON.preferences).to.exist;
expect(userToJSON._id).to.exist;
expect(userToJSON.flags).to.exist;
done();
});

View File

@@ -1,7 +1,7 @@
import moment from 'moment';
import { v4 as generateUUID } from 'uuid';
import validator from 'validator';
import { sleep } from '../../../helpers/api-unit.helper';
import { sleep, translationCheck } from '../../../helpers/api-unit.helper';
import {
SPAM_MESSAGE_LIMIT,
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
@@ -271,7 +271,16 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member attacks Wailing Whale for 5.0 damage.` `Wailing Whale attacks party for 7.5 damage.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`Participating Member attacks Wailing Whale for 5.0 damage. Wailing Whale attacks party for 7.5 damage.`',
info: {
bossDamage: '7.5',
quest: 'whale',
type: 'boss_damage',
user: 'Participating Member',
userDamage: '5.0',
},
});
});
it('applies damage only to participating members of party', async () => {
@@ -344,7 +353,10 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledTwice;
expect(Group.prototype.sendChat).to.be.calledWith('`You defeated Wailing Whale! Questing party members receive the rewards of victory.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`You defeated Wailing Whale! Questing party members receive the rewards of victory.`',
info: { quest: 'whale', type: 'boss_defeated' },
});
});
it('calls finishQuest when boss has <= 0 hp', async () => {
@@ -387,7 +399,10 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledWith(quest.boss.rage.effect('en'));
expect(Group.prototype.sendChat).to.be.calledWith({
message: quest.boss.rage.effect('en'),
info: { quest: 'trex_undead', type: 'boss_rage' },
});
expect(party.quest.progress.hp).to.eql(383.5);
expect(party.quest.progress.rage).to.eql(0);
});
@@ -437,7 +452,10 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledWith(quest.boss.rage.effect('en'));
expect(Group.prototype.sendChat).to.be.calledWith({
message: quest.boss.rage.effect('en'),
info: { quest: 'lostMasterclasser4', type: 'boss_rage' },
});
expect(party.quest.progress.rage).to.eql(0);
let drainedUser = await User.findById(participatingMember._id);
@@ -488,7 +506,15 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member found 5 Bars of Soap.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`Participating Member found 5 Bars of Soap.`',
info: {
items: { soapBars: 5 },
quest: 'atom1',
type: 'user_found_items',
user: 'Participating Member',
},
});
});
it('sends a chat message if no progress is made', async () => {
@@ -499,7 +525,15 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member found 0 Bars of Soap.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`Participating Member found 0 Bars of Soap.`',
info: {
items: { soapBars: 0 },
quest: 'atom1',
type: 'user_found_items',
user: 'Participating Member',
},
});
});
it('sends a chat message if no progress is made on quest with multiple items', async () => {
@@ -516,9 +550,15 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWithMatch(/`Participating Member found/);
expect(Group.prototype.sendChat).to.be.calledWithMatch(/0 Blue Fins/);
expect(Group.prototype.sendChat).to.be.calledWithMatch(/0 Fire Coral/);
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`Participating Member found 0 Fire Coral, 0 Blue Fins.`',
info: {
items: { blueFins: 0, fireCoral: 0 },
quest: 'dilatoryDistress1',
type: 'user_found_items',
user: 'Participating Member',
},
});
});
it('handles collection quests with multiple items', async () => {
@@ -535,8 +575,14 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWithMatch(/`Participating Member found/);
expect(Group.prototype.sendChat).to.be.calledWithMatch(/\d* (Tracks|Broken Twigs)/);
expect(Group.prototype.sendChat).to.be.calledWithMatch({
message: sinon.match(/`Participating Member found/).and(sinon.match(/\d* (Tracks|Broken Twigs)/)),
info: {
quest: 'evilsanta2',
type: 'user_found_items',
user: 'Participating Member',
},
});
});
it('sends message about victory', async () => {
@@ -547,7 +593,10 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledTwice;
expect(Group.prototype.sendChat).to.be.calledWith('`All items found! Party has received their rewards.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`All items found! Party has received their rewards.`',
info: { type: 'all_items_found' },
});
});
it('calls finishQuest when all items are found', async () => {
@@ -718,6 +767,258 @@ describe('Group Model', () => {
expect(res.t).to.not.be.called;
});
});
describe('translateSystemMessages', () => {
it('translate quest_start', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'quest_start',
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate boss_damage', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'boss_damage',
user: questLeader.profile.name,
quest: 'basilist',
userDamage: 15.3,
bossDamage: 3.7,
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate boss_dont_attack', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'boss_dont_attack',
user: questLeader.profile.name,
quest: 'basilist',
userDamage: 15.3,
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate boss_rage', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'boss_rage',
quest: 'lostMasterclasser3',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate boss_defeated', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'boss_defeated',
quest: 'lostMasterclasser3',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate user_found_items', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'user_found_items',
user: questLeader.profile.name,
quest: 'lostMasterclasser1',
items: {
ancientTome: 3,
forbiddenTome: 2,
hiddenTome: 1,
},
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate all_items_found', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'all_items_found',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate spell_cast_party', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'spell_cast_party',
user: questLeader.profile.name,
class: 'wizard',
spell: 'earth',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate spell_cast_user', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'spell_cast_user',
user: questLeader.profile.name,
class: 'special',
spell: 'snowball',
target: participatingMember.profile.name,
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate quest_cancel', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'quest_cancel',
user: questLeader.profile.name,
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate quest_abort', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'quest_abort',
user: questLeader.profile.name,
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate tavern_quest_completed', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'tavern_quest_completed',
quest: 'stressbeast',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate tavern_boss_rage_tired', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'tavern_boss_rage_tired',
quest: 'stressbeast',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate tavern_boss_rage', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'tavern_boss_rage',
quest: 'dysheartener',
scene: 'market',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate tavern_boss_desperation', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'tavern_boss_desperation',
quest: 'stressbeast',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate claim_task', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'claim_task',
user: questLeader.profile.name,
task: 'Feed the pet',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
});
describe('toJSONCleanChat', () => {
it('shows messages with 1 flag to non-admins', async () => {
party.chat = [{
flagCount: 1,
info: {
type: 'quest_start',
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
expect(toJSON.chat.length).to.equal(1);
});
it('shows messages with >= 2 flag to admins', async () => {
party.chat = [{
flagCount: 3,
info: {
type: 'quest_start',
quest: 'basilist',
},
}];
const admin = new User({'contributor.admin': true});
let toJSON = await Group.toJSONCleanChat(party, admin);
expect(toJSON.chat.length).to.equal(1);
});
it('doesn\'t show flagged messages to non-admins', async () => {
party.chat = [{
flagCount: 3,
info: {
type: 'quest_start',
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
expect(toJSON.chat.length).to.equal(0);
});
});
});
context('Instance Methods', () => {
@@ -1007,20 +1308,22 @@ describe('Group Model', () => {
});
it('formats message', () => {
const chatMessage = party.sendChat('a new message', {
_id: 'user-id',
profile: { name: 'user name' },
contributor: {
toObject () {
return 'contributor object';
const chatMessage = party.sendChat({
message: 'a new message', user: {
_id: 'user-id',
profile: { name: 'user name' },
contributor: {
toObject () {
return 'contributor object';
},
},
},
backer: {
toObject () {
return 'backer object';
backer: {
toObject () {
return 'backer object';
},
},
},
});
}}
);
const chat = chatMessage;
@@ -1037,7 +1340,7 @@ describe('Group Model', () => {
});
it('formats message as system if no user is passed in', () => {
const chat = party.sendChat('a system message');
const chat = party.sendChat({message: 'a system message'});
expect(chat.text).to.eql('a system message');
expect(validator.isUUID(chat.id)).to.eql(true);
@@ -1052,7 +1355,7 @@ describe('Group Model', () => {
});
it('updates users about new messages in party', () => {
party.sendChat('message');
party.sendChat({message: 'message'});
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({
@@ -1066,7 +1369,7 @@ describe('Group Model', () => {
type: 'guild',
});
group.sendChat('message');
group.sendChat({message: 'message'});
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({
@@ -1076,7 +1379,7 @@ describe('Group Model', () => {
});
it('does not send update to user that sent the message', () => {
party.sendChat('message', {_id: 'user-id', profile: { name: 'user' }});
party.sendChat({message: 'message', user: {_id: 'user-id', profile: { name: 'user' }}});
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({
@@ -1088,7 +1391,7 @@ describe('Group Model', () => {
it('skips sending new message notification for guilds with > 5000 members', () => {
party.memberCount = 5001;
party.sendChat('message');
party.sendChat({message: 'message'});
expect(User.update).to.not.be.called;
});
@@ -1096,7 +1399,7 @@ describe('Group Model', () => {
it('skips sending messages to the tavern', () => {
party._id = TAVERN_ID;
party.sendChat('message');
party.sendChat({message: 'message'});
expect(User.update).to.not.be.called;
});
@@ -1928,7 +2231,7 @@ describe('Group Model', () => {
await guild.save();
const groupMessage = guild.sendChat('Test message.');
const groupMessage = guild.sendChat({message: 'Test message.'});
await groupMessage.save();
await sleep();

View File

@@ -149,7 +149,7 @@ describe('POST /group', () => {
).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cannotCreatePublicGuildWhenMuted'),
message: t('chatPrivilegesRevoked'),
});
});
});

View File

@@ -100,7 +100,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cannotInviteWhenMuted'),
message: t('chatPrivilegesRevoked'),
});
});
@@ -262,7 +262,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cannotInviteWhenMuted'),
message: t('chatPrivilegesRevoked'),
});
});
@@ -436,7 +436,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cannotInviteWhenMuted'),
message: t('chatPrivilegesRevoked'),
});
});
@@ -526,7 +526,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cannotInviteWhenMuted'),
message: t('chatPrivilegesRevoked'),
});
});

View File

@@ -13,10 +13,10 @@ describe('payments - stripe - #checkout', () => {
});
it('verifies credentials', async () => {
await expect(user.post(endpoint, {id: 123})).to.eventually.be.rejected.and.eql({
await expect(user.post(endpoint, {id: 123})).to.eventually.be.rejected.and.include({
code: 401,
error: 'Error',
message: 'Invalid API Key provided: ****************************1111',
message: 'Invalid API Key provided: aaaabbbb********************1111',
});
});

View File

@@ -127,7 +127,13 @@ describe('POST /groups/:groupId/quests/abort', () => {
members: {},
});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWithMatch(/aborted the party quest Wail of the Whale.`/);
expect(Group.prototype.sendChat).to.be.calledWithMatch({
message: sinon.match(/aborted the party quest Wail of the Whale.`/),
info: {
quest: 'whale',
type: 'quest_abort',
},
});
stub.restore();
});

View File

@@ -141,7 +141,14 @@ describe('POST /groups/:groupId/quests/cancel', () => {
members: {},
});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWithMatch(/cancelled the party quest Wail of the Whale.`/);
expect(Group.prototype.sendChat).to.be.calledWithMatch({
message: sinon.match(/cancelled the party quest Wail of the Whale.`/),
info: {
quest: 'whale',
type: 'quest_cancel',
user: sinon.match.any,
},
});
stub.restore();
});

View File

@@ -80,6 +80,13 @@ describe('shared.ops.buy', () => {
headAccessory_special_redHeadband: true,
headAccessory_special_whiteHeadband: true,
headAccessory_special_yellowHeadband: true,
eyewear_special_blackHalfMoon: true,
eyewear_special_blueHalfMoon: true,
eyewear_special_greenHalfMoon: true,
eyewear_special_pinkHalfMoon: true,
eyewear_special_redHalfMoon: true,
eyewear_special_whiteHalfMoon: true,
eyewear_special_yellowHalfMoon: true,
});
});

View File

@@ -75,6 +75,13 @@ describe('shared.ops.buyMarketGear', () => {
headAccessory_special_redHeadband: true,
headAccessory_special_whiteHeadband: true,
headAccessory_special_yellowHeadband: true,
eyewear_special_blackHalfMoon: true,
eyewear_special_blueHalfMoon: true,
eyewear_special_greenHalfMoon: true,
eyewear_special_pinkHalfMoon: true,
eyewear_special_redHalfMoon: true,
eyewear_special_whiteHalfMoon: true,
eyewear_special_yellowHalfMoon: true,
});
expect(analytics.track).to.be.calledOnce;
});

View File

@@ -209,7 +209,7 @@ describe('shared.ops.purchase', () => {
it('purchases quest bundles', () => {
let startingBalance = user.balance;
let clock = sandbox.useFakeTimers(moment('2017-05-20').valueOf());
let clock = sandbox.useFakeTimers(moment('2019-05-20').valueOf());
let type = 'bundles';
let key = 'featheredFriends';
let price = 1.75;

View File

@@ -8,6 +8,7 @@ import mongo from './mongo'; // eslint-disable-line
import moment from 'moment';
import i18n from '../../website/common/script/i18n';
import * as Tasks from '../../website/server/models/task';
export { translationCheck } from './translate';
afterEach((done) => {
sandbox.restore();

View File

@@ -16,3 +16,9 @@ export function translate (key, variables, language) {
return translatedString;
}
export function translationCheck (translatedString) {
expect(translatedString).to.not.be.empty;
expect(translatedString).to.not.eql(STRING_ERROR_MSG);
expect(translatedString).to.not.match(STRING_DOES_NOT_EXIST_MSG);
}

View File

@@ -12,6 +12,8 @@ div
banned-account-modal
amazon-payments-modal(v-if='!isStaticPage')
payments-success-modal
sub-cancel-modal-confirm(v-if='isUserLoaded')
sub-canceled-modal(v-if='isUserLoaded')
snackbars
router-view(v-if="!isUserLoggedIn || isStaticPage")
template(v-else)
@@ -187,6 +189,8 @@ import notifications from 'client/mixins/notifications';
import { setup as setupPayments } from 'client/libs/payments';
import amazonPaymentsModal from 'client/components/payments/amazonModal';
import paymentsSuccessModal from 'client/components/payments/successModal';
import subCancelModalConfirm from 'client/components/payments/cancelModalConfirm';
import subCanceledModal from 'client/components/payments/canceledModal';
import spellsMixin from 'client/mixins/spells';
import { CONSTANTS, getLocalSetting, removeLocalSetting } from 'client/libs/userlocalManager';
@@ -210,6 +214,8 @@ export default {
amazonPaymentsModal,
bannedAccountModal,
paymentsSuccessModal,
subCancelModalConfirm,
subCanceledModal,
},
data () {
return {

View File

@@ -1,30 +1,48 @@
.promo_armoire_backgrounds_201904 {
.promo_armoire_backgrounds_201906 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -277px;
background-position: 0px -361px;
width: 423px;
height: 147px;
}
.promo_mystery_201904 {
.promo_bronze_quest {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -425px;
background-position: 0px 0px;
width: 360px;
height: 360px;
}
.promo_feathered_friends_bundle {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -620px 0px;
width: 423px;
height: 147px;
}
.promo_floral_sunshine_potions {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -620px -148px;
width: 423px;
height: 147px;
}
.promo_halfmoon_glasses {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -509px;
width: 279px;
height: 147px;
}
.promo_mystery_201905 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -620px -296px;
width: 282px;
height: 147px;
}
.promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -277px;
background-position: -903px -296px;
width: 96px;
height: 69px;
}
.scene_spells {
.scene_hiking {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -328px 0px;
width: 312px;
height: 222px;
}
.scene_yesterdailies_repeatables {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 327px;
height: 276px;
background-position: -361px 0px;
width: 258px;
height: 258px;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 556 KiB

After

Width:  |  Height:  |  Size: 561 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 570 KiB

After

Width:  |  Height:  |  Size: 590 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 KiB

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View File

@@ -123,3 +123,87 @@
}
}
}
#subscription-cancel-modal, #subscription-canceled-modal {
.modal-content {
background: transparent;
}
&.modal-hidden-footer .modal-body {
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
}
.modal-header {
justify-content: center;
padding-top: 24px;
padding-bottom: 0px;
border-top-right-radius: 8px;
border-top-left-radius: 8px;
border-bottom: none;
background: $white;
}
.icon-container {
border-radius: 50%;
width: 64px;
height: 64px;
margin: 0 auto;
margin-bottom: 8px;
}
.modal-footer {
background: $gray-700;
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
justify-content: center;
border-top: none;
.small-text {
font-style: normal;
}
}
.modal-body {
padding-top: 16px;
padding-bottom: 24px;
background: white;
.modal-body-col {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
.btn.btn-primary {
margin-top: 24px;
}
}
h2 {
margin-bottom: 8px;
}
.cancel-text {
color: $gray-50;
line-height: 1.71;
}
.details-block {
background: $gray-700;
border-radius: 4px;
padding: 8px 24px;
margin-top: 16px;
display: flex;
flex-direction: row;
text-align: center;
}
.auto-renew {
margin-top: 16px;
color: $orange-10;
font-style: normal;
}
}
}

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="7" height="38" viewBox="0 0 7 38">
<path fill="none" fill-rule="evenodd" stroke="#FFF" stroke-linecap="square" stroke-width="7" d="M3.5 4v17m0 12v1"/>
</svg>

After

Width:  |  Height:  |  Size: 209 B

View File

@@ -87,7 +87,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
button.btn.btn-secondary.purchase-all(@click='unlock(`skin.${set.keys.join(",skin.")}`)') {{ $t('purchaseAll') }}
#hair.section.customize-section(v-if='activeTopPage === "hair"')
.row.col-12.sub-menu.text-center
.col-3.text-center.sub-menu-item(@click='changeSubPage("color")', :class='{active: activeSubPage === "color"}')
.col-3.text-center.sub-menu-item(@click='changeSubPage("color")', :class='{active: activeSubPage === "color", "offset-2": !editing}')
strong(v-once) {{$t('color')}}
.col-3.text-center.sub-menu-item(@click='changeSubPage("bangs")', :class='{active: activeSubPage === "bangs"}')
strong(v-once) {{$t('bangs')}}
@@ -139,6 +139,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
span 5
button.btn.btn-secondary.purchase-all(@click='unlock(`hair.base.${baseHair4Keys.join(",hair.base.")}`)') {{ $t('purchaseAll') }}
.col-12.customize-options
.head_0.option(v-if="!editing", @click='set({"preferences.hair.base": 0})', :class="[{ active: user.preferences.hair.base === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
.option(v-for='option in baseHair1',
:class='{active: user.preferences.hair.base === option}')
.base.sprite.customize-option(:class="`hair_base_${option}_${user.preferences.hair.color}`", @click='set({"preferences.hair.base": option})')
@@ -1125,7 +1126,10 @@ export default {
return options;
},
eyewear () {
let keys = ['blackTopFrame', 'blueTopFrame', 'greenTopFrame', 'pinkTopFrame', 'redTopFrame', 'whiteTopFrame', 'yellowTopFrame'];
let keys = [
'blackTopFrame', 'blueTopFrame', 'greenTopFrame', 'pinkTopFrame', 'redTopFrame', 'whiteTopFrame', 'yellowTopFrame',
'blackHalfMoon', 'blueHalfMoon', 'greenHalfMoon', 'pinkHalfMoon', 'redHalfMoon', 'whiteHalfMoon', 'yellowHalfMoon',
];
let options = keys.map(key => {
let newKey = `eyewear_special_${key}`;
let option = {};

View File

@@ -29,13 +29,14 @@
.btn.btn-primary(v-if='!group.purchased.plan.dateTerminated && group.purchased.plan.paymentMethod === "Stripe"',
@click='showStripeEdit({groupId: group.id})') {{ $t('subUpdateCard') }}
.btn.btn-sm.btn-danger(v-if='!group.purchased.plan.dateTerminated',
@click='cancelSubscription({group: group})') {{ $t('cancelGroupSub') }}
@click='cancelSubscriptionConfirm({group: group})') {{ $t('cancelGroupSub') }}
</template>
<script>
import moment from 'moment';
import { mapState } from 'client/libs/store';
import paymentsMixin from 'client/mixins/payments';
import { CONSTANTS, getLocalSetting, removeLocalSetting } from 'client/libs/userlocalManager';
export default {
mixins: [paymentsMixin],
@@ -45,8 +46,20 @@ export default {
group: {},
};
},
mounted () {
this.loadGroup();
async mounted () {
await this.loadGroup();
let appState = getLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
if (appState) {
appState = JSON.parse(appState);
if (appState.groupPlanCanceled) {
removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
this.$root.$emit('habitica:subscription-canceled', {
dateTerminated: this.dateTerminated,
isGroup: true,
});
}
}
},
computed: {
...mapState({user: 'user.data'}),

View File

@@ -35,7 +35,7 @@ sidebar-section(:title="$t('questDetailsTitle')")
.grey-progress-bar
.collect-progress-bar(:style="{width: (group.quest.progress.collect[key] / value.count) * 100 + '%'}")
strong {{group.quest.progress.collect[key]}} / {{value.count}}
div.text-right {{parseFloat(user.party.quest.progress.collectedItems) || 0}} items found
div.text-right(v-if='userIsOnQuest') {{parseFloat(user.party.quest.progress.collectedItems) || 0}} items found
.boss-info(v-if='questData.boss')
.row
.col-6

View File

@@ -6,12 +6,12 @@ div
report-flag-modal
send-gems-modal
b-navbar.topbar.navbar-inverse.static-top(toggleable="lg", type="dark", :class="navbarZIndexClass")
b-navbar-brand.brand
b-navbar-brand.brand(aria-label="Habitica")
.logo.svg-icon.d-none.d-xl-block(v-html="icons.logo")
.svg-icon.gryphon.d-xs-block.d-xl-none
b-navbar-toggle(target='menu_collapse').menu-toggle
.quick-menu.mobile-only.form-inline
a.item-with-icon(@click="sync", v-b-tooltip.hover.bottom="$t('sync')")
a.item-with-icon(@click="sync", v-b-tooltip.hover.bottom="$t('sync')", :aria-label="$t('sync')")
.top-menu-icon.svg-icon(v-html="icons.sync")
notification-menu.item-with-icon
user-dropdown.item-with-icon
@@ -64,13 +64,13 @@ div
.top-menu-icon.svg-icon(v-html="icons.hourglasses", v-b-tooltip.hover.bottom="$t('mysticHourglassesTooltip')")
span {{ userHourglasses }}
.item-with-icon
.top-menu-icon.svg-icon.gem(v-html="icons.gem", @click='showBuyGemsModal("gems")', v-b-tooltip.hover.bottom="$t('gems')")
a.top-menu-icon.svg-icon.gem(:aria-label="$t('gems')", href="#buy-gems" v-html="icons.gem", @click.prevent='showBuyGemsModal("gems")', v-b-tooltip.hover.bottom="$t('gems')")
span {{userGems}}
.item-with-icon.gold
.top-menu-icon.svg-icon(v-html="icons.gold", v-b-tooltip.hover.bottom="$t('gold')")
.top-menu-icon.svg-icon(:aria-label="$t('gold')", v-html="icons.gold", v-b-tooltip.hover.bottom="$t('gold')")
span {{Math.floor(user.stats.gp * 100) / 100}}
.form-inline.desktop-only
a.item-with-icon(@click="sync", v-b-tooltip.hover.bottom="$t('sync')")
a.item-with-icon(@click="sync", @keyup.enter="sync", role="link", :aria-label="$t('sync')", tabindex="0", v-b-tooltip.hover.bottom="$t('sync')")
.top-menu-icon.svg-icon(v-html="icons.sync")
notification-menu.item-with-icon
user-dropdown.item-with-icon
@@ -290,6 +290,7 @@ div
margin-right: 24px;
}
&:focus /deep/ .top-menu-icon.svg-icon,
&:hover /deep/ .top-menu-icon.svg-icon {
color: $white;
}

View File

@@ -1,7 +1,7 @@
<template lang="pug">
menu-dropdown.item-notifications(:right="true", @toggled="handleOpenStatusChange", :openStatus="openStatus")
div(slot="dropdown-toggle")
div(v-b-tooltip.hover.bottom="$t('notifications')")
div(:aria-label="$t('notifications')", v-b-tooltip.hover.bottom="$t('notifications')")
message-count(
v-if='notificationsCount > 0',
:count="notificationsCount",

View File

@@ -1,7 +1,7 @@
<template lang="pug">
menu-dropdown.item-user(:right="true")
div(slot="dropdown-toggle")
div(v-b-tooltip.hover.bottom="$t('user')")
div(:aria-label="$t('user')", v-b-tooltip.hover.bottom="$t('user')")
message-count(v-if='user.inbox.newMessages > 0', :count="user.inbox.newMessages", :top="true")
.top-menu-icon.svg-icon.user(v-html="icons.user")
.user-dropdown(slot="dropdown-content")

View File

@@ -400,7 +400,7 @@ export default {
// List of prompts for user on changes. Sounds like we may need a refactor here, but it is clean for now
if (!this.user.flags.welcomed) {
this.$store.state.avatarEditorOptions.editingUser = false;
if (this.$store.state.avatarEditorOptions) this.$store.state.avatarEditorOptions.editingUser = false;
return this.$root.$emit('bv::show::modal', 'avatar-modal');
}

View File

@@ -0,0 +1,67 @@
<template lang="pug">
b-modal#subscription-cancel-modal(
size='sm',
:hideFooter="true",
:modalClass="['modal-hidden-footer']"
)
div(slot="modal-header")
.icon-container.warning-container.d-flex.align-items-center.justify-content-center
.svg-icon.warning(v-html="icons.warning", v-once)
.row
.col-12.modal-body-col
h2 {{ config && config.group ? $t('cancelGroupSub') : $t('cancelSub') }}
span.cancel-text {{ config && config.group ? $t('confirmCancelGroupPlan') : $t('confirmCancelSub') }}
button.btn.btn-danger.mt-4.mb-3(v-once, @click="close(); cancelSubscription(config)") {{ $t('cancelSub') }}
a.standard-link(v-once, @click="close()") {{ $t('neverMind') }}
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
#subscription-cancel-modal .modal-header {
border-top: 8px solid $maroon-100;
.warning-container {
background: $maroon-100;
}
.warning {
width: 7px;
height: 38px;
color: white;
}
}
</style>
<script>
import warningIcon from 'assets/svg/exclamation.svg';
import closeIcon from 'assets/svg/close.svg';
import paymentsMixin from 'client/mixins/payments';
export default {
mixins: [paymentsMixin],
data () {
return {
icons: Object.freeze({
warning: warningIcon,
close: closeIcon,
}),
config: null,
};
},
mounted () {
this.$root.$on('habitica:cancel-subscription-confirm', (config) => {
this.config = config;
this.$root.$emit('bv::show::modal', 'subscription-cancel-modal');
});
},
destroyed () {
this.$root.$off('habitica:cancel-subscription-confirm');
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'subscription-cancel-modal');
},
},
};
</script>

View File

@@ -0,0 +1,100 @@
<template lang="pug">
b-modal#subscription-canceled-modal(
size='sm',
:hideFooter="true",
:modalClass="['modal-hidden-footer']"
)
div(slot="modal-header")
.svg-icon.close(v-html="icons.close", v-once, @click="close()")
.icon-container.check-container.d-flex.align-items-center.justify-content-center
.svg-icon.check(v-html="icons.check", v-once)
.row
.col-12.modal-body-col
h2 {{ $t(isGroup ? 'canceledGroupPlan' : 'subCanceledTitle') }}
.details-block
span
| {{ $t('subWillBecomeInactive') }}
br
strong {{ isGroup ? groupDateTerminated : dateTerminated }}
span.auto-renew.small-text(v-once) {{ $t('paymentCanceledDisputes') }}
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
#subscription-canceled-modal .modal-header {
border-top: 8px solid #1CA372;
.check-container {
background: #1CA372;
}
.check {
width: 35.1px;
height: 28px;
color: white;
}
.close {
position: absolute;
top: 24px;
right: 20px;
width: 10px;
height: 10px;
margin: 0;
padding: 0;
cursor: pointer;
&:hover {
color: 878190;
}
}
}
#subscription-canceled-modal .modal-body {
h2 {
margin-bottom: 0px;
}
.details-block {
line-height: 24px;
}
}
</style>
<script>
import checkIcon from 'assets/svg/check.svg';
import closeIcon from 'assets/svg/close.svg';
import paymentsMixin from 'client/mixins/payments';
export default {
mixins: [paymentsMixin],
data () {
return {
icons: Object.freeze({
check: checkIcon,
close: closeIcon,
}),
groupDateTerminated: null,
isGroup: null,
};
},
mounted () {
this.$root.$on('habitica:subscription-canceled', ({dateTerminated, isGroup}) => {
this.isGroup = isGroup;
if (isGroup) {
this.groupDateTerminated = dateTerminated;
}
this.$root.$emit('bv::show::modal', 'subscription-canceled-modal');
});
},
destroyed () {
this.$root.$off('habitica:subscription-canceled');
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'subscription-canceled-modal');
},
},
};
</script>

View File

@@ -68,7 +68,7 @@
div(v-if='hasSubscription')
.btn.btn-primary(v-if='canEditCardDetails', @click='showStripeEdit()') {{ $t('subUpdateCard') }}
.btn.btn-sm.btn-danger(v-if='canCancelSubscription && !loading', @click='cancelSubscription()') {{ $t('cancelSub') }}
.btn.btn-sm.btn-danger(v-if='canCancelSubscription && !loading', @click='cancelSubscriptionConfirm()') {{ $t('cancelSub') }}
small(v-if='!canCancelSubscription && !hasCanceledSubscription', v-html='getCancelSubInfo()')
.subscribe-pay(v-if='!hasSubscription || hasCanceledSubscription')
@@ -104,7 +104,6 @@
<script>
import axios from 'axios';
import moment from 'moment';
import filter from 'lodash/filter';
import sortBy from 'lodash/sortBy';
import min from 'lodash/min';
@@ -235,10 +234,6 @@ export default {
amount: this.numberOfMysticHourglasses,
};
},
dateTerminated () {
if (!this.user.preferences || !this.user.preferences.dateFormat) return this.user.purchased.plan.dateTerminated;
return moment(this.user.purchased.plan.dateTerminated).format(this.user.preferences.dateFormat.toUpperCase());
},
canCancelSubscription () {
return (
this.user.purchased.plan.paymentMethod !== this.paymentMethods.GOOGLE &&

View File

@@ -205,6 +205,7 @@
.column-background {
position: absolute;
width: 100%;
bottom: 32px;
&.initial-description {

View File

@@ -3,7 +3,7 @@ A simplified dropdown component that doesn't rely on buttons as toggles like bo
-->
<template lang="pug">
.habitica-menu-dropdown.dropdown(@click="toggleDropdown()", :class="{open: isOpen}")
.habitica-menu-dropdown.dropdown(@click="toggleDropdown()", @keypress.enter.space.stop.prevent="toggleDropdown()", role="button", tabindex="0", :class="{open: isOpen}", :aria-pressed="isPressed")
.habitica-menu-dropdown-toggle
slot(name="dropdown-toggle")
.dropdown-menu(:class="{'dropdown-menu-right': right}")
@@ -12,10 +12,16 @@ A simplified dropdown component that doesn't rely on buttons as toggles like bo
<style lang="scss">
@import '~client/assets/scss/colors.scss';
.habitica-menu-dropdown {
&:hover,
&:focus { // NB focus styles match the hover styles for .svg-icon
outline: none;
}
.habitica-menu-dropdown.open {
.habitica-menu-dropdown-toggle .svg-icon {
color: $white !important;
&.open {
.habitica-menu-dropdown-toggle .svg-icon {
color: $white !important;
}
}
}
</style>
@@ -66,6 +72,9 @@ export default {
if (this.openStatus !== undefined) return this.openStatus === 1 ? true : false;
return this.isDropdownOpen;
},
isPressed () {
return this.isOpen ? 'true' : 'false';
},
},
mounted () {
document.documentElement.addEventListener('click', this._clickOutListener);

View File

@@ -35,7 +35,7 @@
.time
span.mr-1(v-if='conversation.username') @{{ conversation.username }}
span {{ conversation.date | timeAgo }}
div {{conversation.lastMessageText ? conversation.lastMessageText.substring(0, 30) : ''}}
div.messagePreview {{ conversation.lastMessageText ? removeTags(parseMarkdown(conversation.lastMessageText)) : '' }}
.col-8.messages.d-flex.flex-column.justify-content-between
.empty-messages.text-center(v-if='!selectedConversation.key')
.svg-icon.envelope(v-html="icons.messageIcon")
@@ -209,6 +209,16 @@
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>
@@ -218,6 +228,7 @@ import filter from 'lodash/filter';
import sortBy from 'lodash/sortBy';
import groupBy from 'lodash/groupBy';
import { mapState } from 'client/libs/store';
import habiticaMarkdown from 'habitica-markdown';
import styleHelper from 'client/mixins/styleHelper';
import toggleSwitch from 'client/components/ui/toggleSwitch';
import axios from 'axios';
@@ -389,7 +400,7 @@ export default {
if (this.user.flags.chatRevoked) {
return {
title: this.$t('PMPlaceholderTitleRevoked'),
description: this.$t('PMPlaceholderDescriptionRevoked'),
description: this.$t('chatPrivilegesRevoked'),
};
}
return {
@@ -500,6 +511,15 @@ export default {
if (!message.contributor) return;
return this.icons[`tier${message.contributor.level}`];
},
removeTags (html) {
let tmp = document.createElement('DIV');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
},
parseMarkdown (text) {
if (!text) return;
return habiticaMarkdown.render(String(text));
},
},
};
</script>

View File

@@ -14,7 +14,7 @@
.svg-icon.positive-icon(v-html="icons.positive")
button.btn.btn-secondary.positive-icon(v-if='this.userLoggedIn.contributor.admin && !adminToolsLoaded',
@click="loadAdminTools()", v-b-tooltip.hover.right="'Admin - Load Tools'")
.svg-icon.positive-icon(v-html="icons.edit")
.svg-icon.positive-icon(v-html="icons.staff")
span(v-if='this.userLoggedIn.contributor.admin && adminToolsLoaded')
button.btn.btn-secondary.positive-icon(v-if='!hero.flags || (hero.flags && !hero.flags.chatRevoked)',
@click="adminRevokeChat()", v-b-tooltip.hover.bottom="'Admin - Revoke Chat Privileges'")
@@ -407,7 +407,7 @@ import megaphone from 'assets/svg/broken-megaphone.svg';
import lock from 'assets/svg/lock.svg';
import challenge from 'assets/svg/challenge.svg';
import member from 'assets/svg/member-icon.svg';
import edit from 'assets/svg/edit.svg';
import staff from 'assets/svg/tier-staff.svg';
export default {
props: ['userId', 'startingPage'],
@@ -430,7 +430,7 @@ export default {
challenge,
lock,
member,
edit,
staff,
}),
adminToolsLoaded: false,
userIdToMessage: '',

View File

@@ -5,6 +5,9 @@ export default function markdown (el, {value, oldValue}) {
if (value) {
el.innerHTML = habiticaMarkdown.render(String(value));
} else {
el.innerHTML = '';
}
el.classList.add('markdown');
}

View File

@@ -8,13 +8,14 @@ import notificationsMixin from 'client/mixins/notifications';
import * as Analytics from 'client/libs/analytics';
import { CONSTANTS, setLocalSetting } from 'client/libs/userlocalManager';
import pick from 'lodash/pick';
import moment from 'moment';
const habiticaUrl = `${location.protocol}//${location.host}`;
export default {
mixins: [notificationsMixin],
computed: {
...mapState(['credentials']),
...mapState({user: 'user.data', credentials: 'credentials'}),
paypalCheckoutLink () {
return '/paypal/checkout';
},
@@ -31,6 +32,10 @@ export default {
if (this.subscription.coupon) couponString = `&coupon=${this.subscription.coupon}`;
return `/paypal/subscribe?sub=${this.subscription.key}${couponString}`;
},
dateTerminated () {
if (!this.user.preferences || !this.user.preferences.dateFormat) return this.user.purchased.plan.dateTerminated;
return moment(this.user.purchased.plan.dateTerminated).format(this.user.preferences.dateFormat.toUpperCase());
},
},
methods: {
encodeGift (uuid, gift) {
@@ -275,10 +280,10 @@ export default {
this.amazonPayments.groupToCreate = null;
this.amazonPayments.group = null;
},
cancelSubscriptionConfirm (config) {
this.$root.$emit('habitica:cancel-subscription-confirm', config);
},
async cancelSubscription (config) {
if (config && config.group && !confirm(this.$t('confirmCancelGroupPlan'))) return;
if (!confirm(this.$t('sureCancelSub'))) return;
this.loading = true;
let group;
@@ -286,18 +291,10 @@ export default {
group = config.group;
}
let paymentMethod = this.user.purchased.plan.paymentMethod;
if (group) {
paymentMethod = group.purchased.plan.paymentMethod;
}
let paymentMethod = group ? group.purchased.plan.paymentMethod : this.user.purchased.plan.paymentMethod;
paymentMethod = paymentMethod === 'Amazon Payments' ? 'amazon' : paymentMethod.toLowerCase();
if (paymentMethod === 'Amazon Payments') {
paymentMethod = 'amazon';
} else {
paymentMethod = paymentMethod.toLowerCase();
}
let queryParams = {
const queryParams = {
noRedirect: true,
};
@@ -309,9 +306,19 @@ export default {
const cancelUrl = `/${paymentMethod}/subscribe/cancel?${encodeParams(queryParams)}`;
await axios.get(cancelUrl);
alert(this.$t('paypalCanceled'));
// @TODO: We should probably update the api to return the new sub data eventually.
await this.$store.dispatch('user:fetch', {forceLoad: true});
if (!config || !config.group) {
await this.$store.dispatch('user:fetch', {forceLoad: true});
this.$root.$emit('habitica:subscription-canceled', {
dateTerminated: this.dateTerminated,
isGroup: false,
});
} else {
const appState = {
groupPlanCanceled: true,
};
setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(appState));
window.location.reload(true);
}
this.loading = false;
} catch (e) {

View File

@@ -1,15 +1,14 @@
HabitRPG's translations are managed on [Transifex](https://www.transifex.com/projects/p/habitrpg/).
HabitRPG's translations are managed at http://translate.habitica.com/
The files in this folder are automatically pulled from Transifex, with
The files in this folder are automatically pulled from there, with
exception of the original American-English strings which are managed
directly through GitHub in `locales/en`.
When you need to change any text, edit only the files in `locales/en`.
Do not edit files in any other locales directory. You do not need to
request that your changes be translated; changes are automatically
copied to Transifex on a regular basis.
copied to the translation website on a regular basis.
If you want to help with translations, please first read [Guidance for
Linguists](http://habitica.fandom.com/wiki/Guidance_for_Linguists) and
note especially its information about the [Translations Trello
card](https://trello.com/c/SvTsLdRF/12-translations).
note especially its information about the [Translations Trello card](https://trello.com/c/SvTsLdRF/12-translations).

View File

@@ -489,5 +489,21 @@
"backgroundHalflingsHouseText": "Halfling's House",
"backgroundHalflingsHouseNotes": "Visit a charming Halfling's House.",
"backgroundBlossomingDesertText": "Blossoming Desert",
"backgroundBlossomingDesertNotes": "Witness a rare superbloom in the Blossoming Desert."
"backgroundBlossomingDesertNotes": "Witness a rare superbloom in the Blossoming Desert.",
"backgrounds052019": "SET 60: Released May 2019",
"backgroundDojoText": "Dojo",
"backgroundDojoNotes": "Learn new moves in a Dojo.",
"backgroundParkWithStatueText": "Park with Statue",
"backgroundParkWithStatueNotes": "Follow a flower-lined path through a Park with a Statue.",
"backgroundRainbowMeadowText": "Rainbow Meadow",
"backgroundRainbowMeadowNotes": "Find the pot of gold where a Rainbow ends in a Meadow.",
"backgrounds062019": "SET 61: Released June 2019",
"backgroundSchoolOfFishText": "School of Fish",
"backgroundSchoolOfFishNotes": "Swim among a School of Fish.",
"backgroundSeasideCliffsText": "Seaside Cliffs",
"backgroundSeasideCliffsNotes": "Stand on a beach with the beauty of Seaside Cliffs above.",
"backgroundUnderwaterVentsText": "Underwater Vents",
"backgroundUnderwaterVentsNotes": "Take a deep dive down, down to the Underwater Vents."
}

View File

@@ -175,6 +175,8 @@
"youCast": "You cast <%= spell %>.",
"youCastTarget": "You cast <%= spell %> on <%= target %>.",
"youCastParty": "You cast <%= spell %> for the party.",
"chatCastSpellParty": "<%= username %> casts <%= spell %> for the party.",
"chatCastSpellUser": "<%= username %> casts <%= spell %> on <%= target %>.",
"critBonus": "Critical Hit! Bonus: ",
"gainedGold": "You gained some Gold",
"gainedMana": "You gained some Mana",

View File

@@ -282,6 +282,8 @@
"hatchingPotionRoseQuartz": "Rose Quartz",
"hatchingPotionCelestial": "Celestial",
"hatchingPotionVeggie": "Garden",
"hatchingPotionSunshine": "Sunshine",
"hatchingPotionBronze": "Bronze",
"hatchingPotionNotes": "Pour this on an egg, and it will hatch as a <%= potText(locale) %> pet.",
"premiumPotionAddlNotes": "Not usable on quest pet eggs.",

View File

@@ -267,7 +267,7 @@
"missingNewPassword": "Missing new password.",
"invalidEmailDomain": "You cannot register with emails with the following domains: <%= domains %>",
"wrongPassword": "Wrong password.",
"incorrectDeletePhrase": "Please type <%= magicWord %> in all caps to delete your account.",
"incorrectDeletePhrase": "Please type <%= magicWord %> in all capital letters to delete your account.",
"notAnEmail": "Invalid email address.",
"emailTaken": "Email address is already used in an account.",
"newEmailRequired": "Missing new email address.",
@@ -283,7 +283,7 @@
"passwordResetEmailHtml": "If you requested a password reset for <strong><%= username %></strong> on Habitica, <a href=\"<%= passwordResetLink %>\">click here</a> to set a new one. The link will expire after 24 hours.<br/><br>If you haven't requested a password reset, please ignore this email.",
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
"invalidCredentials": "There is no account that uses those credentials.",
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your username.",
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the Community Guidelines (https://habitica.com/static/community-guidelines) or Terms of Service (https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please include your @Username in the email.",
"accountSuspendedTitle": "Account has been suspended",
"unsupportedNetwork": "This network is not currently supported.",
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",

View File

@@ -422,6 +422,10 @@
"weaponArmoireJugglingBallsNotes": "Habiticans are master multi-taskers, so you should have no trouble keeping all these balls in the air! Increases Intelligence by <%= int %>. Enchanted Armoire: Independent Item.",
"weaponArmoireSlingshotText": "Slingshot",
"weaponArmoireSlingshotNotes": "Take aim at your red Dailies! Increases Strength by <%= str %>. Enchanted Armoire: Independent Item.",
"weaponArmoireNephriteBowText": "Nephrite Bow",
"weaponArmoireNephriteBowNotes": "This bow shoots special jade-tipped arrows that will take down even your most stubborn bad habits! Increases Intelligence by <%= int %> and Strength by <%= str %>. Enchanted Armoire: Nephrite Archer Set (Item 1 of 3).",
"weaponArmoireBambooCaneText": "Bamboo Cane",
"weaponArmoireBambooCaneNotes": "Perfect for assisting you in a stroll, or for dancing the Charleston. Increases Intelligence, Perception, and Constitution by <%= attrs %> each. Enchanted Armoire: Boating Set (Item 3 of 3).",
"armor": "armor",
"armorCapitalized": "Armor",
@@ -912,6 +916,10 @@
"armorArmoireChefsJacketNotes": "This thick cotton jacket is double-breasted to protect you from spills (and conveniently reversible…). Increases Intelligence by <%= int %>. Enchanted Armoire: Chef Set (Item 2 of 4).",
"armorArmoireVernalVestmentText": "Vernal Vestment",
"armorArmoireVernalVestmentNotes": "This silky garment is perfect for enjoying mild spring weather in style. Increases Strength and Intelligence by <%= attrs %> each. Enchanted Armoire: Vernal Vestments Set (Item 2 of 3).",
"armorArmoireNephriteArmorText": "Nephrite Armor",
"armorArmoireNephriteArmorNotes": "Made from strong steel rings and decorated with jade, this armor will protect you from procrastination! Increases Strength by <%= str %> and Perception by <%= per %>. Enchanted Armoire: Nephrite Archer Set (Item 3 of 3).",
"armorArmoireBoatingJacketText": "Boating Jacket",
"armorArmoireBoatingJacketNotes": "Whether you're on a swanky yacht or in a jalopy, you'll be the cat's meow in this jacket and tie. Increases Strength, Intelligence, and Perception by <%= attrs %> each. Enchanted Armoire: Boating Set (Item 1 of 3).",
"headgear": "helm",
"headgearCapitalized": "Headgear",
@@ -1416,6 +1424,10 @@
"headArmoireVernalHenninNotes": "More than just a pretty hat, this conical chapeau can also hold a rolled-up To-Do list inside. Increases Perception by <%= per %>. Enchanted Armoire: Vernal Vestments Set (Item 1 of 3).",
"headArmoireTricornHatText": "Tricorn Hat",
"headArmoireTricornHatNotes": "Become a revolutionary jokester! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
"headArmoireNephriteHelmText": "Nephrite Helm",
"headArmoireNephriteHelmNotes": "The carved jade plume atop this helm is enchanted to enhance your aim. Increases Perception by <%= per %> and Intelligence by <%= int %>. Enchanted Armoire: Nephrite Archer Set (Item 2 of 3).",
"headArmoireBoaterHatText": "Boater Hat",
"headArmoireBoaterHatNotes": "This straw chapeau is the bee's knees! Increases Strength, Constitution, and Perception by <%= attrs %> each. Enchanted Armoire: Boating Set (Item 2 of 3).",
"offhand": "off-hand item",
"offhandCapitalized": "Off-Hand Item",
@@ -1732,6 +1744,8 @@
"backMystery201812Notes": "Your luxurious tail shimmers like an icicle, bobbing happily as you pad softly over the snowdrifts. Confers no benefit. December 2018 Subscriber Item.",
"backMystery201805Text": "Phenomenal Peacock Tail",
"backMystery201805Notes": "This gorgeous feathery tail is perfect for a strut down a lovely garden path! Confers no benefit. May 2018 Subscriber Item.",
"backMystery201905Text": "Dazzling Dragon Wings",
"backMystery201905Notes": "Fly to untold realms with these iridescent wings. Confers no benefit. May 2019 Subscriber Item.",
"backSpecialWonderconRedText": "Mighty Cape",
"backSpecialWonderconRedNotes": "Swishes with strength and beauty. Confers no benefit. Special Edition Convention Item.",
@@ -1900,6 +1914,8 @@
"headAccessoryMystery201804Notes": "These fuzzy sound-catchers will ensure you never miss the rustle of a leaf or the sound of an acorn falling! Confers no benefit. April 2018 Subscriber Item.",
"headAccessoryMystery201812Text": "Arctic Fox Ears",
"headAccessoryMystery201812Notes": "You hear the subtle sound of snowflakes falling upon the landscape. Confers no benefit. December 2018 Subscriber Item.",
"headAccessoryMystery201905Text": "Dazzling Dragon Horns",
"headAccessoryMystery201905Notes": "These horns are as sharp as they are shimmery. Confers no benefit. May 2019 Subscriber Item.",
"headAccessoryMystery301405Text": "Headwear Goggles",
"headAccessoryMystery301405Notes": "\"Goggles are for your eyes,\" they said. \"Nobody wants goggles that you can only wear on your head,\" they said. Hah! You sure showed them! Confers no benefit. August 3015 Subscriber Item.",
@@ -1928,6 +1944,21 @@
"eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
"eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialBlackHalfMoonText": "Black Half-Moon Eyeglasses",
"eyewearSpecialBlackHalfMoonNotes": "Glasses with a black frame and crescent lenses. Confers no benefit.",
"eyewearSpecialBlueHalfMoonText": "Blue Half-Moon Eyeglasses",
"eyewearSpecialBlueHalfMoonNotes": "Glasses with a blue frame and crescent lenses. Confers no benefit.",
"eyewearSpecialGreenHalfMoonText": "Green Half-Moon Eyeglasses",
"eyewearSpecialGreenHalfMoonNotes": "Glasses with a green frame and crescent lenses. Confers no benefit.",
"eyewearSpecialPinkHalfMoonText": "Pink Half-Moon Eyeglasses",
"eyewearSpecialPinkHalfMoonNotes": "Glasses with a pink frame and crescent lenses. Confers no benefit.",
"eyewearSpecialRedHalfMoonText": "Red Half-Moon Eyeglasses",
"eyewearSpecialRedHalfMoonNotes": "Glasses with a red frame and crescent lenses. Confers no benefit.",
"eyewearSpecialWhiteHalfMoonText": "White Half-Moon Eyeglasses",
"eyewearSpecialWhiteHalfMoonNotes": "Glasses with a white frame and crescent lenses. Confers no benefit.",
"eyewearSpecialYellowHalfMoonText": "Yellow Half-Moon Eyeglasses",
"eyewearSpecialYellowHalfMoonNotes": "Glasses with a yellow frame and crescent lenses. Confers no benefit.",
"eyewearSpecialAetherMaskText": "Aether Mask",
"eyewearSpecialAetherMaskNotes": "This mask has a mysterious history. Increases Intelligence by <%= int %>.",

View File

@@ -84,7 +84,7 @@
"continue": "Continue",
"accept": "Accept",
"reject": "Reject",
"neverMind": "Never mind",
"neverMind": "Nevermind",
"buyMoreGems": "Buy More Gems",
"notEnoughGems": "Not enough Gems",
"alreadyHave": "Whoops! You already have this item. No need to buy it again!",

View File

@@ -137,7 +137,6 @@
"PMPlaceholderTitle": "Nothing Here Yet",
"PMPlaceholderDescription": "Select a conversation on the left",
"PMPlaceholderTitleRevoked": "Your chat privileges have been revoked",
"PMPlaceholderDescriptionRevoked": "You are not able to send private messages because your chat privileges have been revoked. If you have questions or concerns about this, please email <a href=\"mailto:admin@habitica.com\">admin@habitica.com</a> to discuss it with the staff.",
"PMReceive": "Receive Private Messages",
"PMEnabledOptPopoverText": "Private Messages are enabled. Users can contact you via your profile.",
"PMDisabledOptPopoverText": "Private Messages are disabled. Enable this option to allow users to contact you via your profile.",
@@ -266,9 +265,7 @@
"userRequestsApproval": "<%= userName %> requests approval",
"userCountRequestsApproval": "<%= userCount %> members request approval",
"youAreRequestingApproval": "You are requesting approval",
"chatPrivilegesRevoked": "You cannot do that because your chat privileges have been revoked.",
"cannotCreatePublicGuildWhenMuted": "You cannot create a public guild because your chat privileges have been revoked.",
"cannotInviteWhenMuted": "You cannot invite anyone to a guild or party because your chat privileges have been revoked.",
"chatPrivilegesRevoked": "You cannot do this because your chat privileges have been removed. For details or to ask if your privileges can be returned, please email our Community Manager at admin@habitica.com or ask your parent or guardian to email them. Please include your @Username in the email. If a moderator has already told you that your chat ban is temporary, you do not need to send an email.",
"newChatMessagePlainNotification": "New message in <%= groupName %> by <%= authorName %>. Click here to open the chat page!",
"newChatMessageTitle": "New message in <%= groupName %>",
"exportInbox": "Export Messages",
@@ -348,8 +345,8 @@
"leaderCannotLeaveGroupWithActiveGroup": "A leader can not leave a group while the group has an active plan",
"youHaveGroupPlan": "You have a free subscription because you are a member of a group that has a Group Plan. This will end when you are no longer in the group that has a Group Plan. Any months of extra subscription credit you have will be applied at the end of the Group Plan.",
"cancelGroupSub": "Cancel Group Plan",
"confirmCancelGroupPlan": "Are you sure you want to cancel the group plan and remove its benefits from all members, including their free subscriptions?",
"canceledGroupPlan": "Canceled Group Plan",
"confirmCancelGroupPlan": "Are you sure you want to cancel your Group Plan? All Group members will lose their subscription and benefits.",
"canceledGroupPlan": "Group Plan Canceled",
"groupPlanCanceled": "Group Plan will become inactive on",
"purchasedGroupPlanPlanExtraMonths": "You have <%= months %> months of extra group plan credit.",
"addManager": "Assign Manager",

View File

@@ -56,7 +56,7 @@
"messageGroupChatFlagAlreadyReported": "You have already reported this message",
"messageGroupChatNotFound": "Message not found!",
"messageGroupChatAdminClearFlagCount": "Only an admin can clear the flag count!",
"messageCannotFlagSystemMessages": "You cannot flag a system message. If you need to report a violation of the Community Guidelines related to this message, please email a screenshot and explanation to Lemoness at <%= communityManagerEmail %>.",
"messageCannotFlagSystemMessages": "You cannot report a system message. If you need to report a violation of the Community Guidelines related to this message, please email a screenshot and explanation to our Community Manager at <%= communityManagerEmail %>.",
"messageGroupChatSpam": "Whoops, looks like you're posting too many messages! Please wait a minute and try again. The Tavern chat only holds 200 messages at a time, so Habitica encourages posting longer, more thoughtful messages and consolidating replies. Can't wait to hear what you have to say. :)",
"messageCannotLeaveWhileQuesting": "You cannot accept this party invitation while you are in a quest. If you'd like to join this party, you must first abort your quest, which you can do from your party screen. You will be given back the quest scroll.",

View File

@@ -121,6 +121,7 @@
"paymentYouSentSubscription": "You sent <strong><%= name %></strong> a <%= months %>-months Habitica subscription.",
"paymentSubBilling": "Your subscription will be billed <strong>$<%= amount %></strong> every <strong><%= months %> months</strong>.",
"paymentAutoRenew": "This subscription will auto-renew until it is canceled. If you need to cancel this subscription, you can do so from your settings.",
"paymentCanceledDisputes": "Weve sent a cancelation confirmation to your email. If you dont see the email, please contact us to prevent future billing disputes.",
"success": "Success!",
"classGear": "Class Gear",

Some files were not shown because too many files have changed in this diff Show More