Compare commits

...

635 Commits

Author SHA1 Message Date
Sabe Jones
020726f6f8 4.101.0 2019-06-18 20:05:15 -05:00
Sabe Jones
9f9b0c73c3 fix(event): 30 days hath July? uh, no 2019-06-18 19:49:55 -05:00
Sabe Jones
2c03c114da chore(sprites): compile 2019-06-18 19:20:23 -05:00
Sabe Jones
8e9500fed2 feat(event): Summer Splash 2019 2019-06-18 19:20:14 -05:00
Sabe Jones
377f849f9e Merge branch 'free-rebirth-limit' into release 2019-06-18 16:02:12 -05:00
Sabe Jones
8bbada540d feat(rebirth): add banner to buy modal 2019-06-18 15:24:50 -05:00
Matteo Pagliazzi
ee867a9d30 add tests 2019-06-17 21:04:22 +02:00
Matteo Pagliazzi
14a4f42b50 indentation 2019-06-17 20:47:55 +02:00
Matteo Pagliazzi
2cbb0a85a4 limit free rebirth to once every 45 days 2019-06-17 20:37:18 +02:00
Sabe Jones
975e068fe2 4.100.2 2019-06-14 12:35:37 +00:00
Sabe Jones
5a5f1d6895 Revert "Performance: Inbox Paging / loading (#11157)"
This reverts commit 5630e8cc8e.
2019-06-14 12:34:38 +00:00
Sabe Jones
1db0cda6b1 4.100.1 2019-06-13 13:14:32 -05:00
Sabe Jones
f987585cf1 fix(quests): content corrections 2019-06-13 13:13:55 -05:00
Sabe Jones
0325ae9636 fix(avatar): style correction 2019-06-13 13:10:06 -05:00
Sabe Jones
4c4fbfe790 fix(backgrounds): put errant BGs on 3x3 grid 2019-06-13 12:22:28 -05:00
Sabe Jones
6b59262e3e Challenge privacy fix (#11222)
* fix(challenges): filter out private content API-side

* fix(challenges): cleaner fix + test
2019-06-13 09:27:47 -05:00
negue
5630e8cc8e Performance: Inbox Paging / loading (#11157)
* load messages per conversation

* only sort ones in ui

*  add contributor to message

* fix correct message layout/message

* mugenScroll on chatMessages

* fix lint, no mugen-scroll, use own scroll handler

* fix height / margin of modal + use button to load more

* fix tests

* user data from inbox

* style "load earlier messages"

*  move mapMessage to the inbox api result / extract sentMessage of members-api-controller

* fix test back

* fix test

* keep last scroll position

* just set the Id of the returned message instead of all other properties

* fix add new messages (buttons were hidden) + load more

* item-mounted debounce to trigger the re-scrolling
2019-06-13 15:18:50 +02:00
Matteo Pagliazzi
5268bbb8a9 fix(subscription): reset lastReminderDate when creating a new subscription 2019-06-12 17:21:08 +02:00
Sabe Jones
34faf2fd50 4.100.0 2019-06-11 15:27:34 -05:00
Sabe Jones
896950dbf0 chore(sprites): compile 2019-06-11 15:26:50 -05:00
Sabe Jones
424c50f2a4 chore(news): Bailey
also direct mod contact form to HTTPS
2019-06-11 15:26:38 -05:00
Sabe Jones
f235a64d96 Merge branch 'sabrecat/pet-achievements' into release 2019-06-11 15:12:34 -05:00
Sabe Jones
286abe334e fix(notifications): add missing enum 2019-06-11 14:50:17 -05:00
Sabe Jones
81333a3074 feat(notifications): alert user to achievements 2019-06-11 13:06:00 -05:00
Matteo Pagliazzi
3c31945524 Email subs reminders release (#11216)
* add subscriptions reminders emails

* add field to record the last sub reminder
2019-06-10 22:37:30 +02:00
Sabe Jones
103945f7c5 feat(content): Dolphin Pet Quest
and revise Mind Over Matter achievement
2019-06-10 14:42:38 -05:00
Sabe Jones
b9d9a17aca 4.99.1 2019-06-06 15:54:18 -05:00
Sabe Jones
a0b6f576d2 Merge branch 'release' into develop 2019-06-04 16:49:17 -05:00
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
e9a5e084fc fix(tests): use baseline quest with fewer update calls 2019-06-04 16:09:01 -05:00
Sabe Jones
12250a93f1 feat(basic-auth): allow multiple auth pairs (#11204) 2019-06-04 15:52:25 -05:00
Sabe Jones
7094e75dd8 feat(achievements): new pet-related cheevos 2019-06-04 10:31:25 -05:00
Sabe Jones
74c93955f8 Merge branch 'release' into develop 2019-06-03 15:22:54 -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
Matteo Pagliazzi
6cd00897ed fix(deps): do not update minor changes 2019-06-02 12:19:11 +02:00
greenkeeper[bot]
3c442318d8 Update js2xmlparser to the latest version 🚀 (#11019)
* fix(package): update js2xmlparser to version 4.0.0

* chore(package): update lockfile package-lock.json

* fix package-lock

* fix js2xmlparser usage
2019-06-01 14:45:05 +02:00
greenkeeper[bot]
fd3f7e2b0e Update pageres to the latest version 🚀 (#10981)
* fix(package): update pageres to version 5.0.0

* chore(package): update lockfile package-lock.json
2019-06-01 14:40:53 +02:00
Matteo Pagliazzi
6f0bd0b913 fix(package-lock.json): fix missing entry 2019-06-01 13:22:00 +02:00
greenkeeper[bot]
5b7621ceb3 fix(package): update merge-stream to version 2.0.0 (#11190) 2019-06-01 13:19:01 +02:00
greenkeeper[bot]
b8b414cae5 Update chromedriver to the latest version 🚀 (#11116)
* chore(package): update chromedriver to version 73.0.0

* chore(package): update lockfile package-lock.json
2019-06-01 09:48:52 +02:00
Sabe Jones
783338f1f0 fix(strings): misformatted variables in pt_BR 2019-05-31 14:56:26 -05:00
HydeHunter2
fb6ca0c73b Add chevron buttons to sub-items in the menu (#11158)
* Add arrow buttons to sub-items

* Fix mistyped in width

* Add 'v-once' to never changing elements(icons)

* Add animation and ability to close tabs

* Fix typo in rotate

* Only 1 opened tab

* Add animation to dropdown elements

* Close menu after clicking tab

* Improved closing the menu after clicking on links

* Remove browser prefixes

* Add 'active' class to some tabs after clicking

* Return ability to open tabs from Desktop

* pin 'currency-tray' at top of menu

* Revert "pin 'currency-tray' at top of menu"

This reverts commit 12d4144a11.

* Fix display of 'Groups' sub-items
2019-05-31 12:51:29 +02:00
greenkeeper[bot]
5792f54aa0 Update validator to the latest version 🚀 (#11188)
* fix(package): update validator to version 11.0.0

* chore(package): update lockfile package-lock.json
2019-05-31 12:41:58 +02:00
greenkeeper[bot]
3c943c8f23 Update gulp-imagemin to the latest version 🚀 (#11195)
* fix(package): update gulp-imagemin to version 6.0.0

* chore(package): update lockfile package-lock.json
2019-05-31 12:28:38 +02:00
garayj
4b2fd60e47 FIX Guild suggestion incorrectly identifies account as new #11159 (#11197)
* Changed the suggested guild string to a translatable string and made changes so that the string only shows within 60 days of user profile creation.

* Removed whitespace.
2019-05-31 12:26:15 +02:00
Alexey Pyltsyn
75281ab638 Remove default color in Instagram icon (#11196) 2019-05-31 12:23:00 +02:00
greenkeeper[bot]
d46c9967fb Update axios to the latest version 🚀 (#11198)
* fix(package): update axios to version 0.19.0

* chore(package): update lockfile package-lock.json
2019-05-31 12:19:53 +02: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
Sabe Jones
0a840ca952 4.93.4 2019-05-03 08:47:40 -05:00
HydeHunter2
c0837e3b3c 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:56 +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
Sabe Jones
d8ea3bd23a 4.93.3 2019-05-02 14:25:07 -05:00
Sabe Jones
0ce11b82df Merge branch 'develop' into release 2019-05-02 14:25:02 -05:00
Sabe Jones
b4c47b4afd chore(sprites): compile 2019-05-02 14:24:39 -05:00
Sabe Jones
4777850601 chore(event): end Flinging 2019-05-02 14:24:29 -05:00
Sabe Jones
9a3e208c9b Merge branch 'release' into develop 2019-04-30 14:17:32 -05:00
Sabe Jones
792d5998b0 4.93.2 2019-04-30 14:17:14 -05:00
Sabe Jones
53515fd3f4 chore(news): Bailey 2019-04-30 14:17:05 -05:00
Sabe Jones
946147a4aa 4.93.1 2019-04-29 13:45:04 -05:00
Sabe Jones
983ea7c6c7 fix(strings): mixed-up armor notes 2019-04-29 13:44:52 -05:00
Sabe Jones
1135ab946e Sabrecat/groups quick wins (#11146)
* WIP(groups): quickish wins

* WIP(groups): two quick wins
1. Don't show task creation button if user is not leader or manager
2. Don't require JS confirm() for approving tasks

* fix(group-plans): allow delete from options button

* fix(group-plans): update tasksOrder when task deleted

* fix(group-tasks): dismiss notification when user takes action

* refactor(tasks): DRY out create button styling

* fix(group-tasks): sync after claiming/unclaiming

* fix(claiming): better sync and notif handling

* fix(tasks): force sync instead of explicitly clearing notif

* fix(tasks): reposition task creation button

* fix(group-tasks): default to single completion

* fix(group-tasks): move completion condition field above approval switch

* fix(group-tasks): todo validation error and approval notif dismissal

* fix(group-tasks): default single completion on client

* fix(group-tasks): move completion condition up more

* fix(group-tasks): maintain client-side user assignment list

* fix(group-tasks): remove approval notifications when task deleted

* fix(group-tasks): send assigned task to top of task list

* fix(group-tasks): remove useless tag filter dropdown

* feat(group-tasks): notify user of assigned task

* fix(group-tasks): don't allow approval of tasks w/ no approval request

* fix(tests): adjust expectations

* fix(group-tasks): more sensible action on assignment notif click
2019-04-29 13:38:28 -05:00
HydeHunter2
5a15c73fca Reload after rebirth (#11125)
* Add restart after rebirth

Page will be reloaded after purchasing "Orb of Rebirth"

* Remove restart after closing achievement

This reload is not needed, as the page now reloads immediately after purchasing "Orb of Rebirth"

* Move rebirth notification to modal

* Delete references to rebirth notification
2019-04-27 19:32:33 +02:00
HydeHunter2
3f99c14a37 Seasonal alert removed for items you own (#11135) 2019-04-27 19:28:35 +02:00
HydeHunter2
9e515d96c3 Remove reward button for non-leader/non-admin (#11136) 2019-04-27 19:26:02 +02:00
HydeHunter2
40e0017b17 Separate tags of different types (#11123)
Challenge, group and user tags are separated
2019-04-27 19:22:09 +02:00
HydeHunter2
251563690e Fix tag text overlapping (#11124)
* Fix word-wrapping in user

* Fix word-wrapping in taskModal
2019-04-27 19:21:05 +02:00
negue
83070e211d Inbox: Add API to list conversations (#11110)
* Add API to list inbox conversations

* fix test + add api doc

* use `.lean()`

* orderBy after the the grouped conversations are loaded

* fix ordering
2019-04-26 18:45:05 +02:00
Matteo Pagliazzi
3be075ad43 fix(tests): Items Utils > castItemVal fix fn call 2019-04-26 00:02:53 +02:00
Matteo Pagliazzi
043e0fb819 fix #9514: await user update client sid 2019-04-25 22:49:58 +02:00
Matteo Pagliazzi
9d473cc92e wip: fix setting (some) items values in the hall of heroes (#11133) 2019-04-25 22:41:43 +02:00
Sabe Jones
99dec2eb0c 4.93.0 2019-04-25 13:55:22 -05:00
Sabe Jones
cbb04d221d Merge branch 'develop' into release 2019-04-25 13:55:17 -05:00
Sabe Jones
1b93f20451 chore(sprites): compile 2019-04-25 13:54:58 -05:00
Sabe Jones
0f374abd27 feat(content): subscriber mystery set 2019-04-25 13:42:49 -05:00
Sabe Jones
a4a8ac6c5e Merge branch 'release' into develop 2019-04-23 15:30:37 -05:00
Sabe Jones
3854c6f62f 4.92.6 2019-04-23 15:30:14 -05:00
Sabe Jones
82cd53f8cb chore(news): Bailey 2019-04-23 15:29:15 -05:00
Sabe Jones
6f7cd96e9f Improved number validation (#11131)
* fix(purchasing): more number validation

* test(purchasing): add error cases
Also refactor NaN check and create client mixin

* fix(purchasing): cover "purchase" cases
2019-04-23 15:19:49 -05:00
Phillip Thelen
821fc1d9c0 Fix 500 server error when trying to log in with wrong username (#11126) 2019-04-23 18:38:56 +02:00
negue
e4eca4b767 Challenges: infinite scroll (#11112)
* debounced infinite scroll on challenges

* add back the normal link color
2019-04-19 16:22:37 +02:00
Matteo Pagliazzi
bd3de3c48c Spells Performances (#11104)
* spells: only select needed fields

* fix tests

* add comment
2019-04-19 16:18:32 +02:00
Sabe Jones
9f09a0396b 4.92.5 2019-04-18 14:16:19 -05:00
Sabe Jones
f16e0bd044 chore(news): Bailey
also conclude email A/B testing
2019-04-18 14:15:28 -05:00
Melior
c91de90fff Update from Weblate (#11118)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/en_GB/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/pt_BR/

* Translated using Weblate (Russian)

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/ru/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1767 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (293 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/pt_BR/

* Translated using Weblate (Russian)

Currently translated at 99.6% (292 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ru/

* Translated using Weblate (Russian)

Currently translated at 99.5% (480 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (482 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (153 of 153 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Loginincentives
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (66 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/pt_BR/

* Translated using Weblate (Russian)

Currently translated at 98.4% (65 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ru/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (169 of 169 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (128 of 128 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (144 of 144 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/

* Translated using Weblate (Russian)

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ru/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/pt_BR/

* Translated using Weblate (Russian)

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ru/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/pt_BR/

* Translated using Weblate (Spanish)

Currently translated at 97.8% (415 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/

* Translated using Weblate (Italian)

Currently translated at 96.6% (410 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.1% (416 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.1% (416 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (169 of 169 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (169 of 169 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (169 of 169 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/

* Translated using Weblate (French)

Currently translated at 99.7% (423 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.9% (1766 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.9% (1766 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

* Translated using Weblate (German)

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/

* Translated using Weblate (German)

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (637 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (637 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

* Translated using Weblate (Russian)

Currently translated at 100.0% (482 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (482 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (144 of 144 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (144 of 144 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ru/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (637 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (637 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

* Translated using Weblate (Russian)

Currently translated at 100.0% (144 of 144 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (144 of 144 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ru/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en@pirate/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en@pirate/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en@pirate/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (206 of 206 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (206 of 206 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (206 of 206 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/en@pirate/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (206 of 206 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/en@pirate/

* Translated using Weblate (Russian)

Currently translated at 100.0% (206 of 206 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ru/

* Translated using Weblate (French)

Currently translated at 100.0% (206 of 206 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/fr/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (206 of 206 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/sk/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (7 of 7 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (206 of 206 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/uk/

* Translated using Weblate (Danish)

Currently translated at 100.0% (7 of 7 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/da/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (7 of 7 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/en@pirate/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (7 of 7 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/en@pirate/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hant/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en_GB/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en@pirate/

* Translated using Weblate (German)

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/

* Translated using Weblate (German)

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/

* Translated using Weblate (Hungarian)

Currently translated at 96.6% (410 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hu/

* Translated using Weblate (Russian)

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/

* Translated using Weblate (Polish)

Currently translated at 97.6% (414 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pl/

* Translated using Weblate (Russian)

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/

* Translated using Weblate (Ukrainian)

Currently translated at 97.4% (413 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/uk/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (137 of 137 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (137 of 137 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/zh_Hant/

* Translated using Weblate (Danish)

Currently translated at 100.0% (137 of 137 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/da/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (137 of 137 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/en@pirate/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (137 of 137 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/en_GB/

* Translated using Weblate (French)

Currently translated at 100.0% (137 of 137 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/fr/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (226 of 226 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/

* Translated using Weblate (Russian)

Currently translated at 100.0% (137 of 137 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ru/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (226 of 226 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (226 of 226 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/en@pirate/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (226 of 226 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/en_GB/

* Translated using Weblate (French)

Currently translated at 100.0% (226 of 226 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/fr/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (126 of 126 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/zh_Hans/

* Translated using Weblate (Russian)

Currently translated at 100.0% (226 of 226 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/ru/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (126 of 126 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (126 of 126 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/zh_Hant/

* Translated using Weblate (Danish)

Currently translated at 100.0% (126 of 126 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/da/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (126 of 126 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/en@pirate/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (126 of 126 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/en_GB/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (126 of 126 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/ja/

* Translated using Weblate (Russian)

Currently translated at 100.0% (126 of 126 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (126 of 126 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/ru/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/en@pirate/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/en_GB/

* Translated using Weblate (French)

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/fr/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/es/

* Translated using Weblate (Russian)

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/ru/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (78 of 78 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (78 of 78 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (78 of 78 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/en@pirate/

* Translated using Weblate (Russian)

Currently translated at 100.0% (78 of 78 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/ru/

* Translated using Weblate (French)

Currently translated at 100.0% (78 of 78 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/fr/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (15 of 15 strings)

Translation: Habitica/Death
Translate-URL: https://translate.habitica.com/projects/habitica/death/en@pirate/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (15 of 15 strings)

Translation: Habitica/Death
Translate-URL: https://translate.habitica.com/projects/habitica/death/zh_Hant/

* Translated using Weblate (Russian)

Currently translated at 100.0% (15 of 15 strings)

Translation: Habitica/Death
Translate-URL: https://translate.habitica.com/projects/habitica/death/ru/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (15 of 15 strings)

Translation: Habitica/Death
Translate-URL: https://translate.habitica.com/projects/habitica/death/ja/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (26 of 26 strings)

Translation: Habitica/Defaulttasks
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/en@pirate/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/

* Translated using Weblate (Russian)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/en@pirate/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/en_GB/

* Translated using Weblate (French)

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/fr/

* Translated using Weblate (Polish)

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/pl/

* Translated using Weblate (Russian)

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/ru/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/es/

* Translated using Weblate (Slovak)

Currently translated at 99.6% (332 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/sk/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1767 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1767 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1767 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1767 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hant/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1767 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (1767 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/en@pirate/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (1767 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/en_GB/

* Translated using Weblate (French)

Currently translated at 100.0% (1767 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/

* Translated using Weblate (Polish)

Currently translated at 99.0% (1751 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pl/

* Translated using Weblate (Russian)

Currently translated at 99.3% (1755 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

* Translated using Weblate (Russian)

Currently translated at 99.3% (1755 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

* Translated using Weblate (Russian)

Currently translated at 99.3% (1755 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

* Translated using Weblate (Spanish)

Currently translated at 99.4% (1757 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (293 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (293 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (293 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (293 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/en@pirate/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (293 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/en_GB/

* Translated using Weblate (French)

Currently translated at 100.0% (293 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/fr/

* Translated using Weblate (Japanese)

Currently translated at 99.6% (292 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/

* Translated using Weblate (Russian)

Currently translated at 100.0% (293 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ru/

* Translated using Weblate (Polish)

Currently translated at 100.0% (293 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/pl/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (482 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (482 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (482 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/en@pirate/

* Translated using Weblate (French)

Currently translated at 100.0% (482 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/fr/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (482 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/en_GB/

* Translated using Weblate (Japanese)

Currently translated at 99.3% (479 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/

* Translated using Weblate (Polish)

Currently translated at 99.5% (480 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pl/

* Translated using Weblate (Russian)

Currently translated at 100.0% (482 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (6 of 6 strings)

Translation: Habitica/Inventory
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/zh_Hans/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (6 of 6 strings)

Translation: Habitica/Inventory
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/en@pirate/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (153 of 153 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (153 of 153 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/zh_Hans/

* Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/

* Translated using Weblate (Danish)

Currently translated at 100.0% (153 of 153 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/da/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/en@pirate/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Loginincentives
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/zh_Hans/

* Translated using Weblate (Danish)

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Loginincentives
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/da/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Loginincentives
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/en@pirate/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (31 of 31 strings)

Translation: Habitica/Maintenance
Translate-URL: https://translate.habitica.com/projects/habitica/maintenance/en@pirate/

* Translated using Weblate (Russian)

Currently translated at 100.0% (31 of 31 strings)

Translation: Habitica/Maintenance
Translate-URL: https://translate.habitica.com/projects/habitica/maintenance/ru/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (31 of 31 strings)

Translation: Habitica/Maintenance
Translate-URL: https://translate.habitica.com/projects/habitica/maintenance/en@pirate/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (12 of 12 strings)

Translation: Habitica/Merch
Translate-URL: https://translate.habitica.com/projects/habitica/merch/en@pirate/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (66 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (66 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (66 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/zh_Hant/

* Translated using Weblate (Danish)

Currently translated at 96.9% (64 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/da/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (66 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/en@pirate/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (66 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/en@pirate/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (66 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/en_GB/

* Translated using Weblate (French)

Currently translated at 100.0% (66 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (66 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/fr/

* Translated using Weblate (Russian)

Currently translated at 100.0% (66 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ru/

* Translated using Weblate (Polish)

Currently translated at 98.4% (65 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/pl/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (4 of 4 strings)

Translation: Habitica/Noscript
Translate-URL: https://translate.habitica.com/projects/habitica/noscript/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (169 of 169 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/

* Translated using Weblate (Russian)

Currently translated at 100.0% (169 of 169 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ru/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (169 of 169 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/en@pirate/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/zh_Hans/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/en@pirate/

* Translated using Weblate (Russian)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/ru/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (144 of 144 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (144 of 144 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/zh_Hant/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (144 of 144 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (144 of 144 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/en@pirate/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (144 of 144 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/en_GB/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (128 of 128 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hans/

* Translated using Weblate (Russian)

Currently translated at 100.0% (144 of 144 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ru/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (128 of 128 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (128 of 128 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/en@pirate/

* Translated using Weblate (French)

Currently translated at 100.0% (128 of 128 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/fr/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (128 of 128 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/en_GB/

* Translated using Weblate (Russian)

Currently translated at 100.0% (128 of 128 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/ru/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 99.6% (636 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/en@pirate/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/en_GB/

* Translated using Weblate (French)

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

* Translated using Weblate (Russian)

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ru/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/zh_Hant/

* Translated using Weblate (Danish)

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/da/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/en@pirate/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/en@pirate/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/en_GB/

* Translated using Weblate (French)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/fr/

* Translated using Weblate (Russian)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/ru/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 99.5% (216 of 217 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/en@pirate/

* Translated using Weblate (French)

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/en_GB/

* Translated using Weblate (Russian)

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ru/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hant/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hant/

* Translated using Weblate (Danish)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/da/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/en@pirate/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/en@pirate/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/en_GB/

* Translated using Weblate (Russian)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ru/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/uk/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/en@pirate/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/en@pirate/
2019-04-18 13:50:19 -05:00
Sabe Jones
380fb0abf4 fix(tasks): reposition task creation button 2019-04-18 13:09:59 -05:00
Sabe Jones
43b607aedd fix(tasks): force sync instead of explicitly clearing notif 2019-04-18 13:09:59 -05:00
Sabe Jones
4c832ad36c fix(claiming): better sync and notif handling 2019-04-18 13:09:59 -05:00
Sabe Jones
76ae41875d Group Plans quick wins (#11107)
* WIP(groups): quickish wins

* WIP(groups): two quick wins
1. Don't show task creation button if user is not leader or manager
2. Don't require JS confirm() for approving tasks

* fix(group-plans): allow delete from options button

* fix(group-plans): update tasksOrder when task deleted

* fix(group-tasks): dismiss notification when user takes action

* refactor(tasks): DRY out create button styling

* fix(group-tasks): sync after claiming/unclaiming
2019-04-15 10:48:27 -05:00
HydeHunter2
7a5a856ac6 Add message of cancelled quest in party chat (#11106)
* Add message of cancelled quest in party chat

Issue #11093

* Delete trailing spaces

For successful passing the test

* Add test of cancelled quest's message

Also, added an explanation that partyMembers[1] hasn't accepted the invitation in the 'cancels a quest' test

* Fix: import Group

Import Group to pass Lint syntax test

* Move save function to Promise.all

* Fix moving save to Promise.all
2019-04-14 17:55:20 +02:00
Sabe Jones
0bfd709116 4.92.4 2019-04-11 19:55:40 -05:00
Sabe Jones
d7f6c3bc1b fix(challenges): style in challenges too 2019-04-11 19:55:33 -05:00
Sabe Jones
3eb164adcc fix(chat): restore non-contributor styling 2019-04-11 19:55:33 -05:00
Sabe Jones
7bd2cbcf04 4.92.3 2019-04-11 13:10:54 -05:00
Sabe Jones
087498760a fix(modals): upgrade Bootstrap 2019-04-11 13:10:31 -05:00
Sabe Jones
0e26fcce98 4.92.2 2019-04-11 13:05:14 -05:00
Sabe Jones
8d0060d511 chore(news): Bailey 2019-04-11 13:05:04 -05:00
Sabe Jones
0af94b2b44 Merge branch 'develop' of https://github.com/HabitRPG/habitica into develop 2019-04-11 12:35:12 -05:00
Sabe Jones
a7b5b6e20e Revert "fix(equipment): hack partially addressing #11015"
This reverts commit 0be1f3eb7c.
2019-04-11 12:16:16 -05:00
Melior
5f93aad925 Update from Weblate (#11111)
* Translated using Weblate (German)

Currently translated at 100.0% (206 of 206 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/de/

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Russian)

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/ru/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.8% (412 of 417 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/

Translated using Weblate (Russian)

Currently translated at 100.0% (169 of 169 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1752 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1752 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1752 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (137 of 137 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (417 of 417 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (417 of 417 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/

Translated using Weblate (German)

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (206 of 206 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/pt_BR/

* Translated using Weblate (Russian)

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (424 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (137 of 137 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (226 of 226 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (126 of 126 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (78 of 78 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (15 of 15 strings)

Translation: Habitica/Death
Translate-URL: https://translate.habitica.com/projects/habitica/death/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (26 of 26 strings)

Translation: Habitica/Defaulttasks
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/en_GB/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/pt_BR/

* Translated using Weblate (Russian)

Currently translated at 100.0% (333 of 333 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/ru/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1767 of 1767 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (293 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/pt_BR/

* Translated using Weblate (Russian)

Currently translated at 99.6% (292 of 293 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ru/

* Translated using Weblate (Russian)

Currently translated at 99.5% (480 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (482 of 482 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (153 of 153 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Loginincentives
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (66 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/pt_BR/

* Translated using Weblate (Russian)

Currently translated at 98.4% (65 of 66 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ru/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (169 of 169 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (128 of 128 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (144 of 144 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/

* Translated using Weblate (Russian)

Currently translated at 100.0% (638 of 638 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ru/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/pt_BR/

* Translated using Weblate (Russian)

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ru/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/pt_BR/

* Translated using Weblate (Spanish)

Currently translated at 97.8% (415 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/

* Translated using Weblate (Italian)

Currently translated at 96.6% (410 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/
2019-04-11 12:13:39 -05:00
negue
4289becccc Performance: My Challenges (#11065)
* use the same paging for "myChallenges" only loads 10 per call

* challenges: show loading above the load-more button (instead at the top)

* purple loading message and flat load-more button

* remove comment

* show loadMore button only when the request has 10 entries

* challenge card ui
2019-04-10 22:09:04 +02:00
Sabe Jones
eeddd3f366 Merge branch 'release' into develop 2019-04-09 16:26:42 -05:00
Sabe Jones
6ce2caecf9 4.92.1 2019-04-09 16:26:26 -05:00
Sabe Jones
6b933914ef fix(pets): disallow hatching quest pets with Wacky potions 2019-04-09 16:25:49 -05:00
Sabe Jones
060e68ef95 fix(tests): content coverage for wacky pots 2019-04-09 15:20:25 -05:00
Sabe Jones
a486ded6dd fix(tests): content coverage for wacky pots 2019-04-09 15:20:05 -05:00
Sabe Jones
7884f4ce9a Merge branch 'release' into develop 2019-04-09 15:03:03 -05:00
Sabe Jones
1c82fa012d 4.92.0 2019-04-09 15:02:35 -05:00
Sabe Jones
924723bce6 feat(content): Garden Potions and Spring Avatar Customizations 2019-04-09 14:59:30 -05:00
Sabe Jones
b696dde6ba fix(veggies): Call them Garden Potions 2019-04-09 14:36:36 -05:00
Sabe Jones
e8d0557cb6 Merge branch 'release' into sabrecat/veggie-potions 2019-04-09 10:28:03 -05:00
Sabe Jones
cdb6acd4a0 fix(veggies): Flying Pig alignment 2019-04-09 10:27:03 -05:00
Matteo Pagliazzi
128bd4b9cd fix(challenges): check if subscribed only when in group 2019-04-07 15:57:11 +02:00
Michael Toth
fc5f6a31ee Updates classlocked gear item description (#11108) 2019-04-07 14:30:23 +02:00
Sabe Jones
1f0fa500bb fix(footer): remove unofficial Reddit link 2019-04-05 17:16:11 -05:00
Sabe Jones
36276d5d3f 4.91.2 2019-04-05 08:43:21 -05:00
Sabe Jones
5c2c87f523 fix(veggies): pinning and sorting 2019-04-04 10:35:22 -05:00
Sabe Jones
c4f2dafc95 Merge branch 'sabrecat/20190401' into sabrecat/veggie-potions 2019-04-04 09:21:05 -05:00
Alys
f09b65e108 add swear word - TRIGGER / CONTENT WARNING: assault, slurs, swearwords, etc
This adds "pewdiepie" to the list of banned words because
"subscribe to pewdiepie" is semi-common spam in the Tavern,
and because there is sometimes hate speech in the YouTube
channel and in some material posted by pewdiepie's fans.
2019-04-03 20:54:58 +10:00
Randi Miller
a0f42b0e3e Add min-width to PM for wrapping fixes #11060 (#11081) 2019-04-02 16:22:23 -05:00
Matteo Pagliazzi
e10655a5b4 fix(logs): fix FCM logging 2019-04-02 23:19:01 +02:00
Matteo Pagliazzi
d519940931 Merge branch 'develop' of github.com:HabitRPG/habitica into develop 2019-04-02 23:05:16 +02:00
Matteo Pagliazzi
58a43f51d8 downgrade mongoose 2019-04-02 23:04:59 +02:00
Sabe Jones
d25a5fcd57 Merge branch 'release' into develop 2019-04-02 15:55:35 -05:00
Sabe Jones
4085d7a5bb 4.91.1 2019-04-02 15:54:39 -05:00
Matteo Pagliazzi
6c4e1a326f fix(logger): log promise in unhandledRejection event 2019-04-02 22:32:57 +02:00
Sabe Jones
9cf8c0a824 Merge branch 'release' into develop 2019-04-02 13:09:46 -05:00
Sabe Jones
e1f9643ffd 4.91.0 2019-04-02 13:09:16 -05:00
Sabe Jones
1d5c1d5a5f chore(sprites): compile 2019-04-02 13:08:52 -05:00
Sabe Jones
df7c0a005c feat(content): Armoire and Backgrounds March 2019 2019-04-02 13:08:40 -05:00
Matteo Pagliazzi
c60481ab34 Amplitude fixes v2 (#11100)
* fix(analytics): properly catch and log errors

* misc

* refactor

* more refactor

* fallback for user id

* fix tests
2019-04-02 18:44:39 +02:00
Sabe Jones
13818b7634 fix(scripts): update admin migrations
Refactore "full stable" to current format, add email to GDPR deletion output, fix path in bulk email script
2019-04-02 16:35:46 +00:00
Sabe Jones
b2e834c74c Merge branch 'release' into develop 2019-04-02 07:54:59 -05:00
Sabe Jones
4a9cfe8ce5 4.90.4 2019-04-02 07:54:33 -05:00
Sabe Jones
2419b219ba chore(event): end April Fool's
and fix end date of Gala items
2019-04-02 07:53:37 -05:00
Matteo Pagliazzi
0b8ce63c76 WIP: Improve User model performances (#10832)
* wip: define items as mixed objects

* add default owned gear

* mark modified

* more mark modified

* more mark modified

* more mark modified

* more mark modified

* fix common tests

* fix common tests

* update mongoose

* add itemsUtils

* use new util function in hall controller

* add tests for items utils

* update website/server to mark all items as modified

* start updating common code

* update login incentives

* update unlock

* remove changes to package-lock.json

* remove changes to package.json
2019-04-01 19:24:18 +02:00
Sabe Jones
95e541ae75 Merge branch 'release' into develop 2019-04-01 10:27:24 -05:00
Sabe Jones
85c6c19235 4.90.3 2019-04-01 10:26:04 -05:00
Sabe Jones
07e4b2c463 fix(event): include stable 2019-04-01 10:24:55 -05:00
Sabe Jones
7b2081ab03 chore(sprites): compile 2019-04-01 10:17:32 -05:00
Sabe Jones
e749e42665 feat(event): April Fools 2019 2019-04-01 10:17:05 -05:00
negue
0b82722d27 performance: private messages - API (#11077)
* paging for inbox

* clean up
2019-03-31 20:52:53 +02:00
negue
f35ef3a046 Multiple checks for pinnedItems (#11031)
* remove null/undefined entries from pinnedItems when an item is toggled - more inner checks + test

* drawer: fix when there isn't a page available

* rollback cleaning up pinnedEntries on item-toggle

* remove "re-setting" pinnedItems

* remove the filter
2019-03-31 20:41:37 +02:00
Matteo Pagliazzi
5656b9c6ca fix(challenge): allow members to be loaded correctly, fix #11091 2019-03-31 20:36:06 +02:00
Matteo Pagliazzi
1e3d7acf06 update deps 2019-03-31 20:17:10 +02:00
Matteo Pagliazzi
b7e391e074 Merge branch 'develop' of github.com:HabitRPG/habitica into develop 2019-03-31 20:09:12 +02:00
greenkeeper[bot]
b2368e7804 Update superagent to the latest version 🚀 (#11090)
* fix(package): update superagent to version 5.0.2

* chore(package): update lockfile package-lock.json
2019-03-31 20:07:42 +02:00
greenkeeper[bot]
85880d6bb5 Update nodemailer to the latest version 🚀 (#11084)
* fix(package): update nodemailer to version 6.0.0

* chore(package): update lockfile package-lock.json
2019-03-31 20:06:45 +02:00
greenkeeper[bot]
352b8143f3 Update amplitude-js to the latest version 🚀 (#11073)
* fix(package): update amplitude-js to version 5.0.0

* chore(package): update lockfile package-lock.json
2019-03-31 20:06:12 +02:00
Matteo Pagliazzi
7fe3870297 Merge branch 'greenkeeper/karma-4.0.1' into develop 2019-03-31 20:05:26 +02:00
Matteo Pagliazzi
aff32b0e71 Merge branch 'develop' into greenkeeper/karma-4.0.1 2019-03-31 20:05:16 +02:00
Matteo Pagliazzi
4ff56b17e7 Merge branch 'greenkeeper/sinon-7.2.4' into develop 2019-03-31 20:01:38 +02:00
Matteo Pagliazzi
03283d2e52 Merge branch 'develop' into greenkeeper/sinon-7.2.4 2019-03-31 20:01:32 +02:00
Matteo Pagliazzi
07909ac93a Merge branch 'greenkeeper/image-size-0.7.0' into develop 2019-03-31 19:58:51 +02:00
Matteo Pagliazzi
506b155cfa Merge branch 'develop' into greenkeeper/image-size-0.7.0 2019-03-31 19:58:42 +02:00
Matteo Pagliazzi
80bd41928c Merge branch 'greenkeeper/nightwatch-1.0.16' into develop 2019-03-31 19:56:13 +02:00
Matteo Pagliazzi
be3bd25f00 Merge branch 'develop' into greenkeeper/nightwatch-1.0.16 2019-03-31 19:55:57 +02:00
greenkeeper[bot]
3e365f2b4e Update csv-stringify to the latest version 🚀 (#10893)
* fix(package): update csv-stringify to version 5.1.0

* chore(package): update lockfile package-lock.json
2019-03-31 19:50:52 +02:00
greenkeeper[bot]
e1b08e3a20 fix(package): update autoprefixer to version 9.4.0 (#10887) 2019-03-31 19:44:42 +02:00
Matteo Pagliazzi
01281b6414 fix(markdown): make sure to only render strings, fix #11080 2019-03-31 19:38:44 +02:00
Sabe Jones
0b65ac6c4f 4.90.2 2019-03-29 13:57:42 -05:00
Sabe Jones
0d12686a10 chore(news): Bailey 2019-03-29 13:57:02 -05:00
Sabe Jones
8c4d1a67ac 4.90.1 2019-03-28 16:23:28 -05:00
Sabe Jones
e58fbcc34c chore(news): Bailey 2019-03-28 16:23:11 -05: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
Matteo Pagliazzi
1a66a680dc Revert "Add <button> and focus styles for keyboard users" (#11089) 2019-03-27 20:56:00 +01:00
Sabe Jones
fc08b753cd Merge branch 'release' into develop 2019-03-26 20:23:22 -05:00
Sabe Jones
7a73f5bb83 4.90.0 2019-03-26 20:22:52 -05:00
Sabe Jones
6ce1f6f32e chore(sprites): compile 2019-03-26 20:22:35 -05:00
Sabe Jones
50278db1d6 feat(content): Mystery Items March 2019 2019-03-26 20:22:26 -05:00
Sabe Jones
1195560b0c feat(content): Veggie Hatching Potions 2019-03-26 15:45:29 -05:00
Sabe Jones
33c639e28b chore(sprites): compile 2019-03-25 20:19:13 -05:00
Sabe Jones
c2b106564f feat(event): April Fool pronk 2019-03-25 17:26:38 -05:00
Matteo Pagliazzi
fe636b9bd2 Merge pull request #10819 from ianoxley/9796-keyboard-a11y
Add <button> and focus styles for keyboard users
2019-03-24 12:29:07 +01:00
Ian Oxley
7835fe1deb Fix outline issue in Firefox 2019-03-23 22:13:03 +00:00
Ian Oxley
8fb0d0899d Fix checkbox alignment for dailies 2019-03-23 22:12:43 +00:00
Matteo Pagliazzi
abb8dc4dc1 Merge pull request #11078 from ChesterSng/11602-display-message-when-social-auth-acc-uses-password
Fixes #11062 Display information message when social authentication account uses password to login
2019-03-23 18:36:12 +01:00
Matteo Pagliazzi
910a76db68 Merge pull request #11064 from randi2kewl/todo-calendar-highlight-today
Added a highlight on today's date in To-Dos cal
2019-03-23 18:29:15 +01:00
Matteo Pagliazzi
2f792d51f8 Merge pull request #11069 from randi2kewl/11047-hall-search-case-insensitive
Fixes #11047 Hall search case insensitive
2019-03-23 18:22:53 +01:00
Matteo Pagliazzi
6cc925b535 Merge pull request #10988 from HabitRPG/sabrecat/platform-analytics-tweak
Set registered platform on analytics only once
2019-03-23 18:19:15 +01:00
Chester Sng
87d86ee632 Add test case for user that uses social authentication 2019-03-23 20:04:09 +08:00
Randi Miller
b387d77128 Lint fixes 2019-03-22 16:18:58 -04:00
Randi Miller
e644ae83fd Overrides for generateUser and test adjustment 2019-03-22 14:44:27 -04:00
Randi Miller
ae21680a5f Add border-radius to highlighted tile 2019-03-22 14:23:04 -04:00
Randi Miller
c1767eca1b Move datepicker css and color fixes 2019-03-22 14:17:36 -04:00
Chester Sng
d20cd1bbf1 Remove unused string constant in front.js 2019-03-22 19:36:13 +08:00
Chester Sng
3e45f5af41 Add check for the existence of user's password before attempting to authenticate 2019-03-22 19:30:00 +08:00
Bonnie L
23cc2b9d21 catch promise rejection when user submits a message to group. fixes #10970 (#10978)
* catch promise rejection when user submits a message to group

* switch response exception handling to try/catch to avoid mixing aysnc/await with promises
2019-03-21 16:54:10 -05:00
JSn1nj4
58c4fcd506 Fix sellModal data caching (#11044)
* Several deps set to "optional" in lock file

This was done automatically when running `npm i` on Windows.

Node v10.15.2
NPM 6.4.1

* Begin working on issue #10687

Key files to look into:
- website/client/components/inventory/item.vue
- website/client/components/shops/market/sellModal.vue

File notes for me:

item.vue: provides wrapper for displaying item data.

sellModel.vue: for displaying items player intends to sell
- lines 10-21: check `item` computed property and load `item` component
- lines 151-153: ask how return line is evaluated to an `item`

The last one could be important to figuring out this caching issue.
Since the property is computed, it's possible that line is evaluating to
a truthy value that Vue is seeing as unchanged, even if the actual
`item` object is different each time.

* Pick up from sellModal.vue:155

This is where the item context seems to be set. The Vue dev tools
indicate that the `<item>` component is updating. It's only the `item`
reference inside of the `<sellModal>` component that isn't updating.

Issue #10687

* Remove (v-once) directive

This was preventing data referenced within nested elements from
updating.

Issue #10687

* Revert package-lock.json to "develop" version

This removes the "optional" settings that were automatically added by NPM to a handful of dependencies while working on PR #11044.

* Attempt to rerun tests

The most recent change shouldn't have caused tests to fail.
2019-03-21 16:52:00 -05:00
Alec Brickner
2878abc130 Center allocatable stat points (#11053) 2019-03-21 16:49:38 -05:00
Dmitry
a8cb6e3409 Second empty head option was removed from edit avatar - hair - style (#11061) 2019-03-21 16:47:17 -05:00
Alys
2caa540006 allow moderators/staff to always see Report button for a flagged message (#11068) 2019-03-21 16:46:00 -05:00
Sabe Jones
a2261e3591 4.89.0 2019-03-21 16:36:16 -05:00
Sabe Jones
90d498ff96 chore(sprites): compile 2019-03-21 16:36:01 -05:00
Sabe Jones
928327e02a feat(content): Magic Hatching Potions March 2019 2019-03-21 16:34:19 -05:00
Randi Miller
f454700722 extending equipment modal hack to address #11015 (#11070)
* extending equipment modal hack to address #11015

* fix(modal): call hack only once, remove more dangling body props
2019-03-21 13:37:35 -05:00
Matteo Pagliazzi
83bce24e1f Merge branch 'develop' into 9796-keyboard-a11y 2019-03-21 16:05:18 +01:00
Sabe Jones
e7979a99e6 Merge branch 'release' into develop 2019-03-19 19:53:54 -05:00
Sabe Jones
5c648af2ea 4.88.0 2019-03-19 19:53:20 -05:00
Sabe Jones
2ad4bee816 chore(sprites): compile 2019-03-19 19:53:11 -05:00
Sabe Jones
e0291cf432 feat(event): Spring Fling 2019 2019-03-19 19:52:59 -05:00
Randi Miller
bac9121153 Changes color scheme for datepicker fixes #11064 2019-03-19 15:44:06 -04:00
Sabe Jones
d178928c45 fix(analytics): remove excess promise layer 2019-03-18 15:51:49 -05:00
Sabe Jones
9a6aa5f443 merge negue-ui-fixes into develop
Squashed commit of the following:

commit d0628da7557e718b4d144283949f6572718d9b45
Author: negue <eugen.bolz@gmail.com>
Date:   Fri Mar 15 19:34:39 2019 +0100

    fix quest-tooltip padding/sizes

commit c16bc0fe4521e01520628ddc84af54930b15e066
Merge: 24e3fe2a6 163f31688
Author: negue <eugen.bolz@gmail.com>
Date:   Fri Mar 15 18:58:51 2019 +0100

    Merge branch 'fix/quest-tooltip-cells' into negue-ui-fixes

commit 24e3fe2a6c8e384722d95f7edd9ce9e4ce16960c
Author: negue <eugen.bolz@gmail.com>
Date:   Fri Mar 15 18:49:47 2019 +0100

    use item margin instead of container padding

commit 03ab975193b2007e24fcbb971fc7d4169bfb3ac7
Author: negue <eugen.bolz@gmail.com>
Date:   Thu Mar 14 21:41:43 2019 +0100

    fix input heights

commit 3aa18f1105a02655066e10a82cf53be9340a6bf1
Author: negue <eugen.bolz@gmail.com>
Date:   Thu Mar 14 20:58:22 2019 +0100

    revert margin-bottom on checklist

commit d8f533fda1d3833ab2f4aeb3bd8001d4a7881e8b
Merge: fbd01ab79 7d45dcfee
Author: Sabe Jones <sabrecat@gmail.com>
Date:   Thu Mar 14 11:10:34 2019 -0500

    Merge branch 'fix/shops-ui' into negue-ui-fixes

commit fbd01ab79d5ea8144c191cd51a9c11216418493a
Merge: 65517d259 546e260e3
Author: Sabe Jones <sabrecat@gmail.com>
Date:   Thu Mar 14 11:09:03 2019 -0500

    Merge branch 'fix/more-ui' into negue-ui-fixes

commit 65517d25947144f57b84fad84ce709ac7d008119
Merge: 5e935230a 2dcec78a4
Author: Sabe Jones <sabrecat@gmail.com>
Date:   Thu Mar 14 11:08:20 2019 -0500

    Merge branch 'fix/tasks-ui' into negue-ui-fixes

commit 546e260e36
Author: negue <eugen.bolz@gmail.com>
Date:   Sat Mar 9 23:13:54 2019 +0100

    fix checkbox check mark

commit 46ea2010f8
Author: negue <eugen.bolz@gmail.com>
Date:   Sat Mar 9 22:48:20 2019 +0100

    remove bottom margin on a collapsed checklist

commit 7d45dcfee5
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Mar 6 22:45:09 2019 +0100

    fix star/empty colors - fix quest layout - countBadge z-index

commit f9b9e75c18
Author: negue <eugen.bolz@gmail.com>
Date:   Mon Mar 4 20:14:49 2019 +0100

    shops - timeTravelers: refactor filter logic

commit 6d3e9e0de0
Author: negue <eugen.bolz@gmail.com>
Date:   Mon Mar 4 20:06:02 2019 +0100

    shops-seasonal: refactor filter logic

commit aa2949e9d1
Author: negue <eugen.bolz@gmail.com>
Date:   Mon Mar 4 19:54:23 2019 +0100

    shops - quest: refactor filter logic + quest items margins

commit ad62b7a358
Author: negue <eugen.bolz@gmail.com>
Date:   Mon Mar 4 19:50:51 2019 +0100

    shop: equipment-cards background + market filter logic (as other pages)

commit 2dcec78a4a
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 21:02:10 2019 +0100

    remove space between notes and checklist

commit 31fab9b66d
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 20:45:59 2019 +0100

    remove margin of checklist items

commit 28b134a19b
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 20:33:43 2019 +0100

    show `Options` instead of  `Show More`

commit cafcfb6112
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 20:30:22 2019 +0100

    remove pointer on disabled task-controls

commit 7acbcc424b
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 20:25:36 2019 +0100

    remove margin of task-title markdown-p-tag

commit f667ab957b
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 20:25:16 2019 +0100

    40px height search + tags button

commit 2080ecb7e8
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 20:24:35 2019 +0100

    show grabbing cursor while dragging

commit 163f316889
Author: negue <eugen.bolz@gmail.com>
Date:   Fri Feb 15 23:26:40 2019 +0100

    replace the questInfo to use table-like behavior, expands to the content
2019-03-18 15:06:32 -05:00
Randi Miller
e277a088ee Added some additional comments 2019-03-17 04:20:21 -04:00
Randi Miller
e083df64e4 fixes #11047 Switches Hall query to use lowercase name 2019-03-17 04:00:50 -04:00
Sabe Jones
dae37c17d6 Merge branch 'release' into develop 2019-03-15 10:37:22 -05:00
Sabe Jones
1d81916674 4.87.3 2019-03-15 10:36:45 -05:00
Sabe Jones
2a225c2376 chore(event): end Pi Day 2019-03-15 10:31:08 -05:00
Ian Oxley
bd9d9d31c3 Update :focus outline for men icons
Make the .svg-icon focus style match its hover style.
2019-03-14 20:44:09 +00:00
Sabe Jones
f1b5ce9c66 Merge branch 'release' into develop 2019-03-14 14:11:25 -05:00
Matteo Pagliazzi
c08b25101b 4.87.2 2019-03-14 19:43:24 +01:00
Matteo Pagliazzi
9fd0e27c66 fix(stripe): fix buying gems 2019-03-14 19:43:10 +01:00
Matteo Pagliazzi
994082a1d8 payments success state: add note about auto renewal 2019-03-14 19:36:56 +01:00
Sabe Jones
0e6e7adb06 4.87.1 2019-03-14 12:26:03 -05:00
Sabe Jones
8486b9631f Merge branch 'develop' into release 2019-03-14 12:25:55 -05:00
Sabe Jones
3f04c8abf5 chore(email): next split test iteration 2019-03-14 12:21:46 -05:00
Sabe Jones
89e1c69728 fix(string): correct set membership for blue hairbow 2019-03-14 11:23:25 -05:00
Sabe Jones
5e935230a4 Merge branch 'release' into develop 2019-03-14 07:32:41 -05:00
Sabe Jones
9ad50be6ca fix(test): update food const 2019-03-14 07:32:32 -05:00
Sabe Jones
1ff3f3d4e7 Merge branch 'release' into develop 2019-03-14 07:05:35 -05:00
Sabe Jones
0b35aefdc9 4.87.0 2019-03-14 07:05:01 -05:00
Sabe Jones
0d374d817c chore(email): end split test 2019-03-14 07:02:45 -05:00
Sabe Jones
034058301d Merge branch 'sabrecat/pi-day' into release 2019-03-14 07:00:52 -05:00
Sabe Jones
ae2d50c5b8 feat(pie): immediate pie for new users 2019-03-14 06:51:47 -05:00
Randi Miller
fff16a86a5 Added a highlight on today's date in To-Dos cal 2019-03-14 03:40:03 -04:00
Matteo Pagliazzi
c7309ae179 fix(amazon): disabled state for button, fix bug where checkout button would not appear 2019-03-13 19:19:30 +01:00
Matteo Pagliazzi
ef42fba049 fix(paypal): button styles 2019-03-13 18:34:41 +01:00
Sabe Jones
d75f926136 Merge branch 'release' into develop 2019-03-12 19:30:52 -05:00
Sabe Jones
78612a91dd 4.86.2 2019-03-12 19:30:09 -05:00
Sabe Jones
8bcd93075b chore(news): Bailey 2019-03-12 19:29:51 -05:00
Sabe Jones
f318afb8cf Revert "replace the questInfo to use table-like behavior, expands to the content (#10995)"
This reverts commit 3ebd37f7cb.
2019-03-12 19:20:56 -05:00
Sabe Jones
b954379f38 Revert "fixes Tasks UI (#11036)"
This reverts commit f548103f4c.
2019-03-12 19:20:40 -05:00
Sabe Jones
e5c060a80b chore(sprites): compile 2019-03-12 17:10:50 -05:00
Sabe Jones
6668aae89b feat(event): Pi Day 2019-03-12 17:10:38 -05:00
Matteo Pagliazzi
9b62804a5c fix tavern/chat ui (#11056)
* add markdown-formatting link - hide textarea if guidelines not accepting - chat ui

* fix item-with-icon height/margins . public guild item member count
2019-03-11 18:32:07 +01:00
negue
d21e29462c fix: shop ui (#11046)
* shop: equipment-cards background + market filter logic (as other pages)

* shops - quest: refactor filter logic + quest items margins

* shops-seasonal: refactor filter logic

* shops - timeTravelers: refactor filter logic

* fix star/empty colors - fix quest layout - countBadge z-index
2019-03-11 18:26:23 +01:00
Matteo Pagliazzi
6bccd2a866 New Payments Buttons (#11045)
* start implementing separate amazon button component

* wio

* switch amazon to new flow

* adjust amazon pay button size

* design buttons and add them to the settings page

* abstract css

* fixes and buy gems modal

* group plans css

* new buttons for gifts, fix padding in settings

* gift modal styles

* final style fixes
2019-03-11 18:23:47 +01:00
negue
caea222330 fix item-with-icon height/margins . public guild item member count 2019-03-09 22:19:55 +01:00
negue
8ecbdc1448 add markdown-formatting link - hide textarea if guidelines not accepting - chat ui 2019-03-09 21:10:20 +01:00
Sabe Jones
ee20b1eea8 4.86.1 2019-03-07 13:30:58 -06:00
Sabe Jones
ff62c6eea0 Merge branch 'develop' into release 2019-03-07 13:30:49 -06:00
Melior
57cd4d44cd Translated using Weblate (German) (#11052)
Currently translated at 100.0% (206 of 206 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/de/

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Russian)

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/ru/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.8% (412 of 417 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/

Translated using Weblate (Russian)

Currently translated at 100.0% (169 of 169 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1752 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1752 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1752 of 1757 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (137 of 137 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (417 of 417 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (417 of 417 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/

Translated using Weblate (German)

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/
2019-03-07 13:30:19 -06:00
Sabe Jones
e989503cfa Merge branch 'develop' into release 2019-03-07 13:28:34 -06:00
Sabe Jones
431cec7634 fix(strings): remove some trailing spaces 2019-03-07 13:28:05 -06:00
Matteo Pagliazzi
2a367ab3a7 Revert "New payments buttons and new Amazon checkout flow (#10940)"
This reverts commit 77f3bb53de.
2019-03-07 16:58:49 +01:00
Matteo Pagliazzi
bc91dd81e9 Revert "fix(amazon): make sure to use up to date data"
This reverts commit 298a79b58d.
2019-03-07 16:58:38 +01:00
Sabe Jones
201085651b Merge branch 'release' into develop 2019-03-05 15:44:04 -06:00
Sabe Jones
bb395a7ad8 4.86.0 2019-03-05 15:43:35 -06:00
Sabe Jones
264aad79ac chore(sprites): compile 2019-03-05 15:43:29 -06:00
Sabe Jones
9c797e6a54 feat(content): Armoire and Backgrounds March 2019 2019-03-05 15:43:19 -06:00
Sabe Jones
e6c3d00665 fix(emails): send split-test mailings for social reg accounts 2019-03-05 14:55:45 -06:00
negue
660928323e fix header design (#11013)
* statsbar-component for a closer tooltip

* fix scss scope, twice
2019-03-04 22:01:45 +01:00
Matteo Pagliazzi
298a79b58d fix(amazon): make sure to use up to date data 2019-03-04 22:00:40 +01:00
Matteo Pagliazzi
77f3bb53de New payments buttons and new Amazon checkout flow (#10940)
* start implementing separate amazon button component

* wio

* switch amazon to new flow
2019-03-04 21:51:17 +01:00
Alec Brickner
17d8a7b706 Fix mismatch between pet and star badge (#11041) 2019-03-03 17:48:51 +01:00
Alec Brickner
1d8a5b1952 Keep mystery items synced with server (#11039) 2019-03-03 17:39:42 +01:00
negue
f548103f4c fixes Tasks UI (#11036)
* show grabbing cursor while dragging

* 40px height search + tags button

* remove margin of task-title markdown-p-tag

* remove pointer on disabled task-controls

* show `Options` instead of  `Show More`

* remove margin of checklist items

* remove space between notes and checklist
2019-03-03 17:28:58 +01:00
negue
2595ccb2b4 refactor filters to work like in guilds/challenges (#11023) 2019-03-03 17:24:59 +01:00
negue
ceb4288b17 Footer Design / Instagram Link (#11022)
* re-add instagram icon+link

* fix donate button hover - smaller icons if not desktop

* lower margin on the small icons

* Update appFooter.vue

remove DevBlog link from footer
2019-03-03 17:22:55 +01:00
negue
3ebd37f7cb replace the questInfo to use table-like behavior, expands to the content (#10995) 2019-03-03 17:17:45 +01:00
Matteo Pagliazzi
ff3df55639 Merge branch 'negue/flagpm' into develop 2019-03-03 17:12:19 +01:00
Matteo Pagliazzi
080c4b3e20 Merge branch 'develop' into negue/flagpm 2019-03-03 17:12:11 +01:00
Alec Brickner
e939799800 Ensure profile page gets synced (#11032) 2019-03-03 17:05:38 +01:00
Sabe Jones
b7797b3e6c 4.85.5 2019-03-01 16:22:33 -06:00
Sabe Jones
55bd35d7d3 chore(monthly): announce Challenges, end potions 2019-03-01 16:22:19 -06:00
Sabe Jones
b9ae03f795 4.85.4 2019-02-28 16:02:53 -06:00
Sabe Jones
75f6398de2 chore(news): Bailey 2019-02-28 16:02:46 -06:00
greenkeeper[bot]
4004887ddd chore(package): update lockfile package-lock.json 2019-02-28 17:23:10 +00:00
greenkeeper[bot]
ef4d761e0c chore(package): update karma to version 4.0.1 2019-02-28 17:23:04 +00:00
Sabe Jones
a1fb702d1e Merge branch 'release' into develop 2019-02-27 16:17:23 -06:00
Sabe Jones
868759d3e8 4.85.3 2019-02-27 16:17:01 -06:00
Matteo Pagliazzi
45a9d6d17b ios: cancel invalid subscriptions without errors (#11035) 2019-02-27 18:48:13 +01:00
Matteo Pagliazzi
cc766d2260 gcp stackdriver tracing: attach user id (#11033) 2019-02-27 18:47:33 +01:00
Sabe Jones
e710a00e74 Merge branch 'release' into develop 2019-02-26 17:53:19 -06:00
Sabe Jones
bad06ba449 4.85.2 2019-02-26 17:52:59 -06:00
Sabe Jones
ccb088e127 Merge branch 'sabrecat/email-split' into release 2019-02-26 17:52:48 -06:00
Sabe Jones
4a4d48aed8 chore(email): expand to 4 variants 2019-02-26 10:14:55 -06:00
Sabe Jones
da8006506b Merge branch 'release' into develop 2019-02-26 10:09:26 -06:00
Sabe Jones
d451d01b18 4.85.1 2019-02-26 10:08:41 -06:00
Sabe Jones
53555a0f16 fix(navbar): update b-vue toggle syntax 2019-02-26 10:07:48 -06:00
Sabe Jones
962236846a Merge branch 'release' into develop 2019-02-25 17:00:41 -06:00
Sabe Jones
aae8f2923c 4.85.0 2019-02-25 17:00:13 -06:00
Sabe Jones
61956336ea chore(sprites): compile 2019-02-25 16:59:37 -06:00
Sabe Jones
0be1f3eb7c fix(equipment): hack partially addressing #11015 2019-02-25 16:59:28 -06:00
Sabe Jones
fe04b56ecc feat(content): February Mystery Items 2019-02-25 16:58:56 -06:00
Matteo Pagliazzi
7e772924f6 4.84.8 2019-02-24 14:06:19 +01:00
Sabe Jones
901d11c61f fix(logging): move Stackdriver init higher up (#11025) 2019-02-24 14:05:59 +01:00
Matteo Pagliazzi
80b15ac5e9 4.84.7 2019-02-23 18:22:59 +01:00
Matteo Pagliazzi
78b49b9c7e gcp: add stackdriver tracing (#11024) 2019-02-23 18:20:09 +01:00
Matteo Pagliazzi
8874558827 fix package-lock.json 2019-02-23 11:05:20 +01:00
Sabe Jones
ea5ce64db6 fix(email): use updated slugs
and genericize logic for easier tweaking later
2019-02-22 15:47:24 -06:00
Sabe Jones
10b69986c0 chore(email): split test for welcome message 2019-02-22 15:13:32 -06:00
Cameron Billings
2d35009bee Update conditional render to only show Apply Sort Options to Party Header text in party modals (#10984) 2019-02-21 13:35:17 -06:00
Chester Sng
d267f09d04 Enable Username to be searched in Hall of Heroes - fixes #10972 (#10980)
* Add if block to search for username if not valid uuid

* Add validationError check

* Modify test case and added test case for username

* Update description of API

* Update Test

* Correct test

* Change placeholder text in heroes.vue

* Refactor code

* Add quotes

* Update hall.js
2019-02-21 13:33:15 -06:00
Sabe Jones
f23dcf59ff 4.84.6 2019-02-21 12:46:58 -06:00
Sabe Jones
8eb9402c0e fix(achievements): better coloration by piyo 2019-02-21 12:44:30 -06:00
Sabe Jones
a7f2579f6c 4.84.5 2019-02-20 14:35:41 -06:00
Sabe Jones
ada09f3d5a fix(challenges): use b-dropdown-form 2019-02-20 14:17:34 -06:00
Sabe Jones
8fdee5a669 4.84.4 2019-02-19 18:07:08 -06:00
Sabe Jones
3e4d245eba chore(sprites): compile 2019-02-19 18:06:30 -06:00
Sabe Jones
b21cd4a2b6 feat(content): Mythical Marvels Bundle
and end Valentine's miscellany
2019-02-19 18:06:21 -06:00
greenkeeper[bot]
e956bbdf79 chore(package): update lockfile package-lock.json 2019-02-18 15:45:04 +00:00
greenkeeper[bot]
446154b97f chore(package): update sinon to version 7.2.4 2019-02-18 15:44:58 +00:00
Sabe Jones
18de42b13d 4.84.3 2019-02-15 16:07:56 -06:00
Sabe Jones
98fd509530 Hotfix: correct issues from PRs rollout (#10993)
* fix(various): correct issues from PRs rollout
1. Send Gems modal opens from profiles again
2. Contributor titles appear on hover again
3. Tags dropdown in tasks appears again
4. Only user's own @mentions get highlighted again

* fix(test): correct order of css classes in expect
2019-02-15 16:01:33 -06:00
Sabe Jones
d932d6d448 4.84.2 2019-02-14 16:06:08 -06:00
Sabe Jones
be4c777382 fix(hall): bogus msg reference and undef fn
Also couple of styling fixes
2019-02-14 15:51:15 -06:00
Sabe Jones
375a1f3156 4.84.1 2019-02-14 13:41:16 -06:00
Sabe Jones
ca2f2ba9ce Merge branch 'develop' into release 2019-02-14 13:39:52 -06:00
Sabe Jones
165ca8737b chore(news): Blog Bailey 2019-02-14 13:25:40 -06:00
Sabe Jones
f137342d88 fix(analytics): still more registeredThrough cleanup 2019-02-12 16:12:53 -06:00
Sabe Jones
94db493974 fix(analytics): don't also send prop along with event 2019-02-12 16:07:14 -06:00
Sabe Jones
ee5c761680 fix(analytics): set reg platform only once 2019-02-12 16:04:14 -06:00
Sabe Jones
b711c1672b fix(lint): use curly apostrophe and single quotes 2019-02-12 13:48:53 -06:00
Sabe Jones
d675e80555 fix(lint): use curly apostrophe and single quotes 2019-02-12 13:48:13 -06:00
Sabe Jones
2c1ca7629d Merge branch 'release' into develop 2019-02-12 07:35:56 -06:00
Sabe Jones
b5d5367363 4.84.0 2019-02-12 07:20:20 -06:00
Sabe Jones
13af1fa88d chore(sprites): compile 2019-02-12 07:20:11 -06:00
Sabe Jones
b721155f01 feat(event): Valentine's 2019 and Magic Hatching Potions 2019-02-12 07:19:58 -06:00
Sabe Jones
b8aacc03e3 Upgrade to NPM Amplitude package (#10952)
* chore(analytics): upgrade to NPM Amplitude pkg

* chore(build): run package-lock on Linux

* refactor(analytics): use preferred Amplitude v3+ syntax

* refactor(analytics): more v3+ syntax

* chore(npm): attempt updating package lock
2019-02-10 19:34:52 +01:00
negue
844d3fbf37 refresh gear overview (#10971)
* refresh bought seasonal gear

* just "subscribe" to the _v change instead of returning the value

* subscribe in vue instead of lib
2019-02-10 19:32:14 +01:00
Sabe Jones
4d1b239231 Track specific items dropped by Armoire (#10977)
* feat(analytics): track specific items dropped by Armoire

* fix(lint): remove console log

* fix(analytics): address test failures

* fix(analytics): add missed if block

* fix(analytics): seriously tho actually fix
2019-02-10 19:30:49 +01:00
negue
b9aaccdf13 refactored the highlightUsers-method, only matches mentions and not emails (#10982) 2019-02-10 19:27:22 +01:00
Matteo Pagliazzi
0155491a68 Upgrade vue (#10983)
* deps: update

* fix tests
2019-02-10 19:03:35 +01:00
Sabe Jones
ef412c7185 Merge branch 'release' into develop 2019-02-08 06:08:05 -06:00
Sabe Jones
c15b55808e 4.83.2 2019-02-08 06:07:41 -06:00
Sabe Jones
28ddebf4a9 fix(sprites): load new spritesheet in Vue 2019-02-08 06:06:58 -06:00
Sabe Jones
5f0919d1c5 4.83.1 2019-02-07 16:32:02 -06:00
negue
0cbd6fb4d7 Merge branch 'develop' of https://github.com/HabitRPG/habitica into negue/flagpm
# Conflicts:
#	website/client/components/chat/chatCard.vue
#	website/client/components/header/menu.vue
2019-02-07 19:16:10 +01:00
Matteo Pagliazzi
2eab8b2c8b Merge branch 'GiacomoLaw-10954-wikiafix' into develop 2019-02-07 18:20:33 +01:00
Matteo Pagliazzi
b35bd18282 Merge branch '10954-wikiafix' of https://github.com/GiacomoLaw/habitica into GiacomoLaw-10954-wikiafix 2019-02-07 18:20:05 +01:00
Chester Sng
732a46d2db Add else block so items with warning notes will not display sell options (#10965) 2019-02-07 18:02:38 +01:00
Justin Horner
4f1d4aa73a Move hr to be contained within hasClass div (#10964) 2019-02-07 17:58:46 +01:00
negue
92f2079b76 fix emails in chat (#10912)
* additional regex checks to ignore the <tag attribute="content">

* extract highlightUsers method - add test

* refactor highlight regex

* refactor the regex without `lookbehind`

* remove unneeded regex
2019-02-07 17:55:36 +01:00
Phillip Thelen
63f5773172 Implement URL handling for profile modal (#10844)
* Implement URL handling for profile modal

* Fix issue where paths would break when using back button

* move tiers import to index
2019-02-07 17:25:30 +01:00
Sabe Jones
93290ec6d5 Merge branch 'release' into develop 2019-02-05 15:44:40 -06:00
Sabe Jones
be6c2a002f 4.83.0 2019-02-05 15:42:57 -06:00
Sabe Jones
e6bd67a53a chore(sprites): compile 2019-02-05 15:42:46 -06:00
Sabe Jones
6ab3bac96c feat(content): Armoire and BGs 2019-02-05 15:42:33 -06:00
Sabe Jones
ee97da1112 Merge branch 'release' into develop 2019-02-05 17:49:22 +00:00
Sabe Jones
3c948beb84 chore(i18n): update locales 2019-02-05 17:43:50 +00:00
Sabe Jones
f590c485dc Merge branch 'release' into develop 2019-02-04 16:52:53 -06:00
Sabe Jones
64063544e6 4.82.3 2019-02-04 16:52:28 -06:00
Sabe Jones
0910f65fc0 chore(news): Bailey 2019-02-04 16:52:17 -06:00
Sabe Jones
fdc0e0f5fe Merge branch 'release' into develop 2019-02-02 13:11:25 -06:00
Sabe Jones
d225a7ca54 4.82.2 2019-02-02 13:11:05 -06:00
Sabe Jones
5b7c4bf03f fix(event): end Cake 2019-02-02 13:10:56 -06:00
Sabe Jones
dddd8269b6 Merge branch 'release' into develop 2019-02-02 13:04:35 -06:00
Sabe Jones
fd724a36ff 4.82.1 2019-02-02 13:03:13 -06:00
Sabe Jones
5b329db357 chore(event): end Winter Wonderland 2019-02-02 13:03:04 -06:00
Sabe Jones
c1cad5c0a9 Merge branch 'release' into develop 2019-01-31 17:12:21 -06:00
Sabe Jones
94b5ed9dab 4.82.0 2019-01-31 17:11:53 -06:00
Sabe Jones
0ac976e8c1 chore(sprites): compile 2019-01-31 17:11:44 -06:00
Sabe Jones
b1f42dcac9 feat(content): Habitica Birthday 2019 2019-01-31 17:10:56 -06:00
Sabe Jones
ed8bd84257 Merge branch 'release' into develop 2019-01-30 09:14:11 -06:00
Sabe Jones
4e8c08ba9b 4.81.1 2019-01-30 09:13:39 -06:00
Sabe Jones
e294ed836d fix(subscription): correct mystery set 2019-01-30 09:13:17 -06:00
Sabe Jones
c86da9783b Merge branch 'release' into develop 2019-01-30 09:06:42 -06:00
Sabe Jones
64a608e7e7 4.81.0 2019-01-28 16:10:57 -06:00
Sabe Jones
767f844cea chore(sprites): compile 2019-01-28 16:10:38 -06:00
Sabe Jones
00a686dcf6 feat(content): subscriber items Jan 2019 2019-01-28 16:10:22 -06:00
negue
0ca3c1f94d fix POST-chat.test (author_name format) 2019-01-27 21:29:45 +01:00
negue
2f699e24d7 update test to use the correct author_name format 2019-01-27 19:05:00 +01:00
negue
faa0611ab2 Merge branch 'develop' of https://github.com/HabitRPG/habitica into negue/flagpm 2019-01-27 18:14:51 +01:00
Giacomo Lawrance
08c8f26b80 Merge branch 'develop' into 10954-wikiafix 2019-01-26 18:03:50 +00:00
Carl Vuorinen
8904c58510 Make profile modal nav tabs better responsive (#10946)
* Make profile modal nav tabs better responsive

Remove offset & specific width  and use flexbox centering
Fixes #10944

* Change profile modal nav height to min-height

So that height changes accordingly if nav elements wrap to second row
2019-01-26 18:59:17 +01:00
greenkeeper[bot]
d10f1304de Update nodemailer to the latest version 🚀 (#10922)
* fix(package): update nodemailer to version 5.0.0

* chore(package): update lockfile package-lock.json
2019-01-26 18:51:52 +01:00
Lucas Heim
e28992060c Staff sweetness duplicated two handed description (#10936)
* Creating default encryption test to improve coverage

* Revert "Creating default encryption test to improve coverage"

This reverts commit 5d5f8efabb.

* Removing two-handed item duplicated description from all locales
2019-01-26 18:48:21 +01:00
Sabe Jones
df860c9401 fix(quests): repair negative quest scrolls on purchase (#10953) 2019-01-26 18:46:52 +01:00
Mira-M
87923b7f0d Add username to challenge csv export (#10956)
- Adds the member's username to the challenge csv
- Updates to unit tests
- Issue #10955

Supports the ability for challenge owners to judge challenge winners
after the added functionality enabling users to modify their name
(display name). The added field ties the user to their results by
their username.
2019-01-26 18:44:48 +01:00
Sabe Jones
b2d6a9474d 4.80.9 2019-01-24 13:35:46 -06:00
Sabe Jones
5fac4a943c chore(news): Bailey 2019-01-24 13:35:32 -06:00
Sabe Jones
5d0be7bc72 Merge branch 'release' into develop 2019-01-24 13:14:19 -06:00
Giacomo Lawrance
9e6394c38c changed wikia links to fandom
Fixes issue #10954
2019-01-24 11:21:52 +00:00
Matteo Pagliazzi
cc97935ffd 4.80.8 2019-01-23 17:20:05 +01:00
Matteo Pagliazzi
6ea4d96830 add extra condition to skip ssl check 2019-01-23 17:19:57 +01:00
Matteo Pagliazzi
a63ba51497 4.80.7 2019-01-23 17:12:13 +01:00
Matteo Pagliazzi
04a7fd25a6 allow skipping SSL check with secret key (#10962) 2019-01-23 17:10:11 +01:00
Sabe Jones
7cb045781b 4.80.6 2019-01-18 15:13:34 -06:00
Sabe Jones
f8d799d55c chore(news): Bailey 2019-01-18 15:13:25 -06:00
Sabe Jones
5bd9fcc99d Merge branch 'release' into develop 2019-01-18 14:59:37 -06:00
Alessio Libardi
84cafc4081 Special Pets that you don't own will only show GreyPaw print | Fixes #10866 (#10949)
* Added isSpecial function to determine wether a pet is special or not

* Special pets that you don't own show only grey pawprint

* PetEgg HatchingPotion icon only visible if the pet is not special
2019-01-18 14:58:38 -06:00
andrzejZdobywca
d77a17112c setting new password using Return key (#10937) 2019-01-18 14:58:18 -06:00
Matteo Pagliazzi
4f9d97d38f 4.80.5 2019-01-18 12:10:15 +01:00
Matteo Pagliazzi
40060e8ff5 update ssl check 2019-01-18 11:43:02 +01:00
Sabe Jones
508d97d374 4.80.4 2019-01-16 10:22:14 -06:00
Sabe Jones
fd125352b7 fix(g1g1): remove additional banner 2019-01-16 10:22:00 -06:00
Sabe Jones
f8bd1be4a3 4.80.3 2019-01-16 06:24:07 -06:00
Sabe Jones
ec37524164 chore(promo): end G1G1 deal 2019-01-15 18:57:10 -06:00
negue
c66d2cb469 refactor properties - added another "inbox" check 2019-01-14 23:17:26 +01:00
Sabe Jones
87b9e72b56 4.80.2 2019-01-14 12:39:03 -06:00
Sabe Jones
962662fe7c chore(news): Bailey 2019-01-14 12:38:35 -06:00
Matteo Pagliazzi
f63d2e47f0 do not show "Notification not found" error messages (#10931)
* start fixing #10391

* do not show snackbars for notifications not found

* revert message changes

* update tests

* add new test

* fix property
2019-01-13 11:43:02 +01:00
Sabe Jones
349a1032b6 4.80.1 2019-01-11 20:00:31 +00:00
Sabe Jones
476131835d chore(i18n): update locales 2019-01-11 19:59:54 +00:00
Sabe Jones
8237b7f2de chore(news): Bailey 2019-01-11 13:37:58 -06:00
Sabe Jones
6ee2b3690a chore(i18n): update locales 2019-01-10 23:28:33 +00:00
Sabe Jones
a08cca807a 4.80.0 2019-01-09 01:38:46 +00:00
Sabe Jones
8c51f36784 chore(i18n): update locales 2019-01-09 01:35:24 +00:00
Sabe Jones
d35f81cdae chore(sprites): compile 2019-01-08 19:30:49 -06:00
Sabe Jones
1d1b25391f feat(content): customizations and dinosaurs 2019-01-08 19:30:41 -06:00
greenkeeper[bot]
ee09c76c08 chore(package): update lockfile package-lock.json 2019-01-08 11:19:57 +00:00
greenkeeper[bot]
c478748436 fix(package): update image-size to version 0.7.0 2019-01-08 11:19:52 +00:00
negue
61606cb69d change to User ID - change mail var _DISPLAYNAME _DISPLAY_NAME 2019-01-04 21:04:41 +01:00
Sabe Jones
ec81c02d72 4.79.0 2019-01-04 20:04:19 +00:00
Sabe Jones
166da3c2f8 chore(i18n): update locales 2019-01-04 20:04:12 +00:00
Sabe Jones
23b72a673d chore(sprites): compile 2019-01-04 13:59:27 -06:00
Sabe Jones
d22b4bb2f7 feat(content): Armoire and Backgrounds Jan 2019 2019-01-04 13:58:51 -06:00
Sabe Jones
aaebd4da77 4.78.2 2019-01-03 22:31:27 +00:00
Sabe Jones
b128e7874e chore(i18n): update locales 2019-01-03 22:31:16 +00:00
Juliusdotsh
1e10e20a24 apidocs should say how to provide comment when flagging #10916 (#10925) 2019-01-03 11:31:03 +01:00
Sabe Jones
b1aeb8ed87 Merge branch 'release' into develop 2019-01-02 17:08:50 -06:00
Sabe Jones
2cb80e2275 4.78.1 2019-01-02 22:14:24 +00:00
Sabe Jones
ee32e24ff2 chore(i18n): update locales 2019-01-02 22:14:05 +00:00
Sabe Jones
af40c437be chore(news): Bailey
also fix erroneously active hatching potion and remove concluded bundle 
from pinned items
2019-01-02 16:10:57 -06:00
Sabe Jones
a99150c485 Merge branch 'release' into develop 2019-01-01 07:14:04 -06:00
Sabe Jones
696b67204d fix(lint): ’ 2019-01-01 07:13:41 -06:00
Sabe Jones
4f3536e887 Merge branch 'release' into develop 2018-12-31 23:48:59 +00:00
Sabe Jones
07e5bf1437 4.78.0 2018-12-31 23:48:21 +00:00
Sabe Jones
d2ca738256 chore(i18n): update locales 2018-12-31 23:48:11 +00:00
Sabe Jones
f7983f39eb feat(event): New Year's 2018-19 2018-12-31 17:44:13 -06:00
Rene Cordier
7c954f7073 Prevent progress being cleared when quest ends (#10870)
* Prevent progress being cleared when quest ends

changing group tests to make sure it keeps user's progress

fix and remove .only() from tests

* fix tests and check null case for clearing up user's quest without resetting progress
2018-12-28 19:16:21 +01:00
Sabe Jones
82d0e737a6 4.77.6 2018-12-27 15:47:36 +00:00
Sabe Jones
f8213aaf1b chore(i18n): update locales 2018-12-27 15:47:25 +00:00
Matteo Pagliazzi
2335ad4167 fix(gems modal): remove duplicate id property, fix #10919 2018-12-27 15:04:14 +01:00
negue
d84631255b extract site urls from translations (#10909) 2018-12-23 19:56:21 +01:00
Raymond Grumney
0b352b9103 Unit Tests; 1 fix to appFooter debug menu (#10894)
* Built

* Update .editorconfig

* Fixed filename ('translaotr.js' => 'translator.js')

* Fixed Filename

* User Unit Tests

* Writing tests for avatar.vue

* writing tests for getters/user.js

* Writing Tests for avatar.vue

* Writing Tests for avatar.vue

* writing tests for store/getters/user.js

* renamed file

* Integrated test into user.test.js

* Reverting

* Fetching most recent version

* +1 Level now adds a level

* updating to current version

* Minor edits to pass lint test

* Removing non-functional Tests

* Cleaning up
2018-12-23 19:45:59 +01:00
negue
19b75c6257 drag tags to reorder (#10911)
* drag tags to reorder

* change color, remove unneeded sortTag-call
2018-12-23 19:45:05 +01:00
negue
b66904a3a7 notification click always shows the modal, setting only controls the notification timeout (#10913) 2018-12-23 19:35:30 +01:00
Phillip Thelen
cfbfec34aa Allow gems and subs to be gifted through in-app-purchases (#10892)
* Allow gems to be gifted through IAPs

* implement non recurring IAP subscriptions

* fix localization issue in error

* fix non renewing subscription handling

* Fix lint error

* fix tests

* move findbyId mock to helper file

* undo package-lock changes

* Fix lint error
2018-12-23 19:20:14 +01:00
Sabe Jones
88f28188a1 4.77.5 2018-12-21 20:45:20 +00:00
Sabe Jones
fce5be2e8f chore(i18n): update locales 2018-12-21 20:44:16 +00:00
Sabe Jones
1d68cbfaa2 chore(news): more Bailey 2018-12-21 14:42:11 -06:00
Sabe Jones
a44222a350 4.77.4 2018-12-20 16:37:04 -06:00
Sabe Jones
4d2510e322 fix(sprites): add missing dragon icon 2018-12-20 16:36:57 -06:00
Sabe Jones
3b5ed33e03 4.77.3 2018-12-20 21:29:58 +00:00
Sabe Jones
0a6bf92b6b chore(i18n): update locales 2018-12-20 21:29:24 +00:00
Sabe Jones
05ae40bc2e fix(content): correct timeframe tag for string 2018-12-20 15:26:17 -06:00
Sabe Jones
57e96ea092 4.77.2 2018-12-20 12:14:38 -06:00
Sabe Jones
ea49b5b8e0 fix(content): add set string for Dec Mystery 2018-12-20 12:14:28 -06:00
Sabe Jones
11a5de714a 4.77.1 2018-12-20 17:25:33 +00:00
Sabe Jones
f74b4d3e73 Merge branch 'develop' into release 2018-12-20 17:24:59 +00:00
Sabe Jones
ab6fdb99af 4.77.0 2018-12-20 17:23:29 +00:00
Sabe Jones
e3efa557dd chore(i18n): update locales 2018-12-20 17:22:57 +00:00
Sabe Jones
a6cea47789 feat(event): Winter Wonderland 2018 2018-12-20 11:11:50 -06:00
Sabe Jones
5a200a88bd chore(sprites): compile 2018-12-20 09:42:04 -06:00
Sabe Jones
545499ea0b WIP(event): partial content build 2018-12-20 09:40:54 -06:00
Sabe Jones
3e7f4229ec Merge branch 'release' into develop 2018-12-19 13:03:20 +00:00
Sabe Jones
56d5f77b6e 4.76.1 2018-12-19 13:02:58 +00:00
Sabe Jones
efdf5a2e16 chore(i18n): update locales 2018-12-19 13:01:40 +00:00
Sabe Jones
add3a2887f fix(css): restore close svg defaults 2018-12-19 06:58:56 -06:00
Sabe Jones
0ed7c7596a Merge branch 'release' into develop 2018-12-18 21:32:39 +00:00
Sabe Jones
55a452694f 4.76.0 2018-12-18 21:32:12 +00:00
Sabe Jones
cd4d5f83ff chore(i18n): update locales 2018-12-18 21:31:56 +00:00
Sabe Jones
8220199e49 Gift 1 Get 1 Promo 2018-19! (#10915)
* feat(subscription): promo banner in modal

* feat(subscription): promo banner on main page

* fix(banners): remove extraneous margin adjustment

* fix(banners): various

* feat(promotion): gift 1, get 1

* fix(promo): various

* chore(promo): add Bailey

* fix(promo): use different email template for promo beneficiary

* fix(promo): turns out Winter is meaningful
2018-12-18 15:28:53 -06:00
Sabe Jones
bd1f6918ba fix(sprites): wolf and penguin tweaks 2018-12-18 12:22:46 -06:00
Ian Oxley
3d99a64e96 Fix outline around dropdown toggle menu in Firefox
When the menu is open and has the focus shift the outline to the
`.habitica-menu-dropdown-toggle` child element.
2018-12-18 15:58:16 +00:00
Ian Oxley
76f9204417 Update CSS to fix notification layout
Set `.row` inside a `.notification` to use `width: 100%`, and set margin
to zero.
2018-12-18 15:31:45 +00:00
Matteo Pagliazzi
5b21e62647 fix(i18n): format 2018-12-18 15:02:03 +01:00
Matteo Pagliazzi
5e76d6df21 fix(i18n): position of $ sign 2018-12-17 13:12:35 +01:00
greenkeeper[bot]
fad59b9a8d chore(package): update lockfile package-lock.json 2018-12-16 20:42:47 +00:00
greenkeeper[bot]
f218a432ec chore(package): update nightwatch to version 1.0.16 2018-12-16 20:42:43 +00:00
Matteo Pagliazzi
7348145b7d fix(success-modal): misc fixes 2018-12-16 15:39:37 +01:00
Sabe Jones
ba2832d21f 4.75.5 2018-12-14 21:22:05 +00:00
Sabe Jones
688f5084a1 chore(i18n): update locales 2018-12-14 21:19:56 +00:00
Sabe Jones
d5955b8889 Merge branch 'develop' into release 2018-12-14 15:15:06 -06:00
Sabe Jones
01fd17ee3f fix(script): revert email query 2018-12-14 15:14:50 -06:00
Sabe Jones
979497dd35 fix(deletion): user delete bugs
Correct lookup in GDPR script, and address a TypeError when deleting a user with no tasks
2018-12-14 00:54:59 +00:00
Sabe Jones
a2c8b8b05c Usernames Misc: Bulk Email and New Party Modal (#10898)
* feat(migrations): genericize bulk email

* chore(migrations): archive one-offs

* feat(usernames): change create party modal to copy username

* fix(modal): styling

* fix(modal): add Chrome clipboard implementation
2018-12-11 14:47:50 -06:00
Sabe Jones
8750701c08 Merge branch 'release' into develop 2018-12-11 20:45:44 +00:00
Matteo Pagliazzi
8441b0a3d6 fix http auth env var 2018-12-09 20:12:50 +01:00
Matteo Pagliazzi
0667695390 Payments: Success Messages (#10903)
* fix v-once

* gems purchase success dialog

* amazon and stripe support for gems message, translations

* subscriptions success message

* subscriptions success message

* group plans success message and many fixes

* finish success messages for payments

* add success modal when gifting from balance
2018-12-09 20:08:01 +01:00
Matteo Pagliazzi
4d322c1bf6 Merge branch 'sukh0128-develop' into develop 2018-12-07 15:20:27 +01:00
Matteo Pagliazzi
a855ddacc7 Merge branch 'develop' of https://github.com/sukh0128/habitica into sukh0128-develop 2018-12-07 15:20:12 +01:00
Matteo Pagliazzi
9d48ef7322 Merge pull request #10896 from AyoubElk/patch-1
Fixed typo in folder path
2018-12-07 15:13:08 +01:00
negue
73ecdced01 remove the "delete" part of the flag notification 2018-12-07 00:13:24 +01:00
negue
1c17b415f0 refactor {group,inbox}-chatReporter variables 2018-12-06 23:55:18 +01:00
Ayoub El Khattabi
95f3315796 Fixed typo in folder path
The tast test:api:unit:watch should watch the path 'test/api/unit/**/*' instead of the non existent 'test/api/v3/unit/**/*'
2018-12-06 11:26:22 +00:00
Erdenesukh Tsendjav
5e232d8c9f Merge branch 'develop' of github.com:sukh0128/habitica into develop 2018-12-04 17:29:16 -06:00
Erdenesukh Tsendjav
13793f8b3c revert 2018-12-04 17:28:27 -06:00
Erdenesukh Tsendjav
55f875f95a Delete package-lock.json 2018-12-04 17:04:55 -06:00
Erdenesukh Tsendjav
14576be374 merged 2018-12-04 16:03:11 -06:00
Erdenesukh Tsendjav
4cb2c26475 The istanbul command string in the gulp-tests.js file for each of the tests had to be changed to just the keyword istanbul instead of pointing to the istanbul file location on WINDOWS machines 2018-12-04 15:55:09 -06:00
negue
b912a83f22 refactor "formatUser" to show the correct user and displayname 2018-11-29 20:15:47 +01:00
negue
fb3a9740bd add display name to the email variables 2018-11-29 20:00:05 +01:00
negue
817c943860 Merge remote-tracking branch 'origin/develop' into negue/flagpm 2018-11-29 19:02:01 +01:00
negue
0aba448c48 fix sentence 2018-11-29 19:01:26 +01:00
Ian Oxley
6b5173ecbf Replace button tag with ARIA button role
Add `role`, `aria-pressed`, `tabindex`, and keypress handlers, and
remove the `<button>` tag.

Add `isPressed` computed function. Use this to set the value of
`aria-pressed`.
2018-11-26 22:34:15 +00:00
Ian Oxley
53d8d2fc6a Fix :focus CSS for the dropdown menu
Add `:focus` style to the `.habitica-menu-dropdown` class, instead of
the `.habitica-menu-dropdown-toggle` class.
2018-11-26 22:31:57 +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
negue
52a7112591 some ui fixes / requested changes 2018-11-20 17:20:58 +01:00
Ian Oxley
fd13771088 Set background on the dropdown toggle
Set the background to `$white` to stop Firefox falling back to the
default grey background for a button.
2018-11-19 19:40:41 +00:00
Ian Oxley
c9d725ec20 Fix +ve / -ve habit control buttons
Align the icons centrally within the positive / negative controls.
2018-11-19 19:34:01 +00:00
negue
3203bffeaa refactor update the message to use .save - fix test 2018-11-18 22:37:09 +01:00
negue
3f47cdd9a2 merge fixes 2018-11-18 22:12:41 +01:00
negue
8b15d94ae1 Merge remote-tracking branch 'origin/develop' into negue/flagpm
# Conflicts:
#	website/client/components/chat/chatCard.vue
#	website/client/components/chat/chatMessages.vue
#	website/common/locales/en/messages.json
2018-11-18 22:04:33 +01:00
negue
8f2435c37c refactor Inbox-mongodb requests 2018-11-08 22:04:55 +01:00
Ian Oxley
302bc899d7 Update reward button and toggle menu
Add `<button>` tag to the 3 dots toggle menu, and the reward control.

Update the CSS to add focus styles, in addition to hover styles, for the
3 dots toggle menu.
2018-11-07 00:06:30 +00:00
Ian Oxley
8048cf9a97 Add <button> tag to shop items
Make the `.item` element a `<button>` so it can receive keyboard focus,
and be activated via the spacebar.
2018-11-06 23:24:34 +00:00
Ian Oxley
20548daccf Use <button> for habits +/- buttons
Use the `<button>` tag instead of a `<div>`. This makes the element
naturally focusable with the keyboard, and makes the HTML more semantic.

Update the CSS to align the icons in the centre of the `<button>`.
2018-11-06 22:55:51 +00:00
Erdenesukh Tsendjav
3d757c7814 trying something 2018-10-24 16:31:46 -05:00
negue
2c250bfcd9 fix tests / lint 2018-10-14 15:28:00 +02:00
negue
75e3f15352 remove the v3 api method 2018-10-10 20:50:10 +02:00
negue
5670be26c7 review fixes 2018-10-10 20:46:43 +02:00
negue
9fc03cb91a cleanup 2018-10-08 19:08:14 +02: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
negue
7e80406181 fix flagging in the new inbox collection - move flag private message to api/v4 2018-10-02 21:23:29 +02:00
negue
60a6f6f2f6 fix merge issues 2018-09-30 15:37:14 +02:00
negue
92d68e5c6e Merge branch 'develop' of https://github.com/HabitRPG/habitica into negue/flagpm
# Conflicts:
#	website/client/components/chat/chatCard.vue
#	website/client/components/chat/chatMessages.vue
#	website/common/locales/en/messages.json
#	website/server/libs/slack.js
2018-09-22 19:18:08 +02: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
negue
6e7d8d93fe fix test import 2018-06-25 20:23:04 +02:00
negue
bc861133e1 fix doc path 2018-06-25 18:48:43 +02:00
negue
31ef21f25c Merge branch 'develop' of https://github.com/HabitRPG/habitica into negue/flagpm
# Conflicts:
#	website/client/store/actions/chat.js
2018-06-25 18:47:47 +02: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
negue
97e1465899 fix ci - revert reporter to user var 2018-06-13 22:55:28 +02:00
negue
7cb0f5145d update margin / color 2018-06-11 22:53:43 +02:00
negue
5b6217a0bf pass userId for admin-calls 2018-06-11 22:39:07 +02:00
negue
09e4c88606 add tests + fix lint 2018-06-08 21:50:52 +02:00
negue
13bae96708 hide report button if already reported 2018-06-08 21:16:11 +02:00
negue
6241001eef remove unneeded try/catch 2018-06-08 21:03:26 +02:00
negue
dc5722d0de report & mark message 2018-06-08 20:26:26 +02:00
negue
e3b2443029 move reportFlagModal to menu.vue 2018-06-08 19:15:21 +02:00
negue
ca5927fe73 sendInboxFlagNotification (for private message flag content) 2018-06-02 19:27:28 +02:00
negue
5a0eed7eae copy chatReporter - fix unknown apiMessages - add api for flagging pm 2018-06-02 18:18:00 +02:00
negue
532881e679 enable flagging private message - v-once for simple strings/icons - fix flag notification 2018-06-02 18:16:24 +02:00
negue
27d763a46c add slack debug calls (logger.info) 2018-06-02 18:14:53 +02: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
1832 changed files with 60395 additions and 47792 deletions

View File

@@ -4,7 +4,7 @@
# Pull Request
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API)
[Please see these instructions for adding a pull request](http://habitica.fandom.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API)
# Requesting a feature
@@ -12,4 +12,4 @@ Habitica uses [Trello](https://trello.com/b/EpoYEYod/habitica) to track feature
# Contributing Code
See [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica#Coders_.28Web_.26_Mobile.29)
See [Contributing to Habitica](http://habitica.fandom.com/wiki/Contributing_to_Habitica#Coders_.28Web_.26_Mobile.29)

View File

@@ -1,4 +1,4 @@
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API for more info)
[//]: # (Note: See http://habitica.fandom.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API for more info)
[//]: # (Put Issue # here, if applicable. This will automatically close the issue if your PR is merged in)
Fixes put_#_and_issue_numer_here

1
.gitignore vendored
View File

@@ -40,6 +40,7 @@ test/client/unit/coverage
test/client/e2e/reports
test/client-old/spec/mocks/translations.js
yarn.lock
.gitattributes
# Elastic Beanstalk Files
.elasticbeanstalk/*

View File

@@ -8,6 +8,7 @@ ENV BASE_URL https://habitica.com
ENV FACEBOOK_KEY 128307497299777
ENV GA_ID UA-33510635-1
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
ENV LOGGLY_CLIENT_TOKEN ab5663bf-241f-4d14-8783-7d80db77089a
ENV NODE_ENV production
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA

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

@@ -7,6 +7,6 @@ Habitica [![Build Status](https://travis-ci.org/HabitRPG/habitica.svg?branch=dev
We need more programmers! Your assistance will be greatly appreciated.
For an introduction to the technologies used and how the software is organized, refer to [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths).
For an introduction to the technologies used and how the software is organized, refer to [Guidance for Blacksmiths](http://habitica.fandom.com/wiki/Guidance_for_Blacksmiths).
To set up a local install of Habitica for development and testing on various platforms, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
To set up a local install of Habitica for development and testing on various platforms, see [Setting up Habitica Locally](http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally).

View File

@@ -8,4 +8,4 @@ minimal dependencies on the developer's local platform. It can be used
on a variety of systems including Windows, Mac OS X, and Linux.
Instructions for using the Habitica Vagrant environment are in
[Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
[Setting up Habitica Locally](http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally).

View File

@@ -61,8 +61,8 @@
"SESSION_SECRET_IV": "12345678912345678912345678912345",
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
"SITE_HTTP_AUTH_ENABLED": "false",
"SITE_HTTP_AUTH_PASSWORD": "password",
"SITE_HTTP_AUTH_USERNAME": "admin",
"SITE_HTTP_AUTH_PASSWORDS": "password,wordpass,passkey",
"SITE_HTTP_AUTH_USERNAMES": "admin,tester,contributor",
"SLACK_FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
"SLACK_FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
"SLACK_SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id",
@@ -77,5 +77,7 @@
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
"TEST_DB_URI": "mongodb://localhost/habitrpg_test",
"TRANSIFEX_SLACK_CHANNEL": "transifex",
"WEB_CONCURRENCY": 1
"WEB_CONCURRENCY": 1,
"SKIP_SSL_CHECK_KEY": "key",
"ENABLE_STACKDRIVER_TRACING": "false"
}

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

@@ -167,7 +167,7 @@ gulp.task('test:content:safe', gulp.series('test:prepare:build', (cb) => {
gulp.task('test:api:unit', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-unit node_modules/mocha/bin/_mocha -- test/api/unit --recursive --require ./test/helpers/start-server'),
testBin('istanbul cover --dir coverage/api-unit node_modules/mocha/bin/_mocha -- test/api/unit --recursive --require ./test/helpers/start-server'),
(err) => {
if (err) {
process.exit(1);
@@ -180,12 +180,12 @@ gulp.task('test:api:unit', (done) => {
});
gulp.task('test:api:unit:watch', () => {
return gulp.watch(['website/server/libs/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit', done => done()));
return gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit', done => done()));
});
gulp.task('test:api-v3:integration', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
testBin('istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
{maxBuffer: 500 * 1024},
(err) => {
if (err) {
@@ -217,7 +217,7 @@ gulp.task('test:api-v3:integration:separate-server', (done) => {
gulp.task('test:api-v4:integration', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v4-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v4 --recursive --require ./test/helpers/start-server'),
testBin('istanbul cover --dir coverage/api-v4-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v4 --recursive --require ./test/helpers/start-server'),
{maxBuffer: 500 * 1024},
(err) => {
if (err) {
@@ -254,4 +254,4 @@ gulp.task('test:api-v3', gulp.series(
'test:api:unit',
'test:api-v3:integration',
done => done()
));
));

View File

@@ -19,7 +19,7 @@ const Timer = require('./utils/timer');
const connectToDb = require('./utils/connect').connectToDb;
const closeDb = require('./utils/connect').closeDb;
const message = '`This party\'s collection quest has been made easier! For details, refer to http://habitica.wikia.com/wiki/User_blog:LadyAlys/Collection_Quests_are_Now_Easier`';
const message = '`This party\'s collection quest has been made easier! For details, refer to http://habitica.fandom.com/wiki/User_blog:LadyAlys/Collection_Quests_are_Now_Easier`';
const timer = new Timer();

View File

@@ -0,0 +1,110 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20181231_nye';
import { model as User } from '../../../website/server/models/user';
import mongoose from 'mongoose';
import { v4 as uuid } from 'uuid';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {'flags.newStuff': true};
let push;
set.migration = MIGRATION_NAME;
if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
set['items.gear.owned.head_special_nye2018'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2018',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
set['items.gear.owned.head_special_nye2017'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2017',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
set['items.gear.owned.head_special_nye2016'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2016',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
set['items.gear.owned.head_special_nye2015'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2015',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
set['items.gear.owned.head_special_nye2014'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2014',
_id: uuid(),
},
];
} else {
set['items.gear.owned.head_special_nye'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye',
_id: uuid(),
},
];
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$set: set, $push: {pinnedItems: {$each: push}}}).exec();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
};
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

@@ -0,0 +1,88 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20190131_habit_birthday';
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 inc = {
'items.food.Cake_Skeleton': 1,
'items.food.Cake_Base': 1,
'items.food.Cake_CottonCandyBlue': 1,
'items.food.Cake_CottonCandyPink': 1,
'items.food.Cake_Shade': 1,
'items.food.Cake_White': 1,
'items.food.Cake_Golden': 1,
'items.food.Cake_Zombie': 1,
'items.food.Cake_Desert': 1,
'items.food.Cake_Red': 1,
'achievements.habitBirthdays': 1,
};
const set = {};
let push;
set.migration = MIGRATION_NAME;
if (typeof user.items.gear.owned.armor_special_birthday2018 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2019'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2019', _id: uuid()}};
} else if (typeof user.items.gear.owned.armor_special_birthday2017 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2018'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2018', _id: uuid()}};
} else if (typeof user.items.gear.owned.armor_special_birthday2016 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2017'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2017', _id: uuid()}};
} else if (typeof user.items.gear.owned.armor_special_birthday2015 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2016'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2016', _id: uuid()}};
} else if (typeof user.items.gear.owned.armor_special_birthday !== 'undefined') {
set['items.gear.owned.armor_special_birthday2015'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2015', _id: uuid()}};
} else {
set['items.gear.owned.armor_special_birthday'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday', _id: uuid()}};
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: push}).exec();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2019-01-15')},
};
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

@@ -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

@@ -0,0 +1,59 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20190618_summer_splash_orcas';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
let set;
if (user && user.items && user.items.pets && typeof user.items.pets['Orca-Base'] !== 'undefined') {
set = { migration: MIGRATION_NAME };
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Orca-Base'] !== 'undefined') {
set = { migration: MIGRATION_NAME, 'items.pets.Orca-Base': 5 };
} else {
set = { migration: MIGRATION_NAME, 'items.mounts.Orca-Base': true };
}
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-18')},
};
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

@@ -17,7 +17,7 @@ function setUpServer () {
setUpServer();
// Replace this with your migration
const processUsers = require('./users/20181122_turkey_day.js');
const processUsers = require('./users/mystery-items.js');
processUsers()
.then(function success () {
process.exit(0);

View File

@@ -1,5 +1,5 @@
/* eslint-disable no-console */
import { sendTxn } from '../../../website/server/libs/email';
import { sendTxn } from '../../website/server/libs/email';
import { model as User } from '../../website/server/models/user';
import moment from 'moment';
import nconf from 'nconf';

View File

@@ -1,67 +1,24 @@
/* eslint-disable no-console */
const MIGRATION_NAME = 'full-stable';
import each from 'lodash/each';
import keys from 'lodash/keys';
import content from '../../website/common/script/content/index';
const migrationName = 'full-stable.js';
const authorName = 'Sabe'; // in case script author needs to know when their ...
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
import { model as User } from '../../website/server/models/user';
const progressCount = 1000;
let count = 0;
/*
* Award users every extant pet and mount
*/
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
let monk = require('monk');
let dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
let query = {
'profile.name': 'SabreCat',
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}
let progressCount = 1000;
let count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
let userPromises = users.map(updateUser);
let lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => {
processUsers(lastUser._id);
});
}
function updateUser (user) {
async function updateUser (user) {
count++;
let set = {
migration: migrationName,
};
const set = {};
set.migration = MIGRATION_NAME;
each(keys(content.pets), (pet) => {
set[`items.pets.${pet}`] = 5;
@@ -88,30 +45,40 @@ function updateUser (user) {
set[`items.mounts.${mount}`] = true;
});
dbUsers.update({_id: user._id}, {$set: set});
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`);
return await User.update({_id: user._id}, {$set: set}).exec();
}
function displayData () {
console.warn(`\n${ count } users processed\n`);
return exiting(0);
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.local.username': 'olson22',
};
function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else {
console.log(msg);
const fields = {
_id: 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],
};
}
}
process.exit(code);
}
module.exports = processUsers;
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_201811';
const MYSTERY_ITEMS = ['head_mystery_201811', 'weapon_mystery_201811'];
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';

View File

@@ -0,0 +1,73 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20190314_pi_day';
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 inc = {
'items.food.Pie_Skeleton': 1,
'items.food.Pie_Base': 1,
'items.food.Pie_CottonCandyBlue': 1,
'items.food.Pie_CottonCandyPink': 1,
'items.food.Pie_Shade': 1,
'items.food.Pie_White': 1,
'items.food.Pie_Golden': 1,
'items.food.Pie_Zombie': 1,
'items.food.Pie_Desert': 1,
'items.food.Pie_Red': 1,
};
const set = {};
set.migration = MIGRATION_NAME;
set['items.gear.owned.head_special_piDay'] = false;
set['items.gear.owned.shield_special_piDay'] = false;
const push = [
{type: 'marketGear', path: 'gear.flat.head_special_piDay', _id: uuid()},
{type: 'marketGear', path: 'gear.flat.shield_special_piDay', _id: uuid()},
];
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: {pinnedItems: {$each: push}}}).exec();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2019-02-15')},
};
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
}
};

4785
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,20 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.75.4",
"version": "4.101.0",
"main": "./website/server/index.js",
"dependencies": {
"@google-cloud/trace-agent": "^3.6.0",
"@slack/client": "^3.8.1",
"accepts": "^1.3.5",
"amazon-payments": "^0.2.7",
"amplitude": "^3.5.0",
"amplitude-js": "^5.0.0",
"apidoc": "^0.17.5",
"apn": "^2.2.0",
"autoprefixer": "^8.5.0",
"aws-sdk": "^2.329.0",
"axios": "^0.18.0",
"autoprefixer": "^9.4.0",
"aws-sdk": "^2.432.0",
"axios": "^0.19.0",
"axios-progress-bar": "^1.2.0",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
@@ -26,16 +28,16 @@
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0",
"babel-runtime": "^6.11.6",
"bcrypt": "^3.0.1",
"bcrypt": "^3.0.5",
"body-parser": "^1.18.3",
"bootstrap": "^4.1.1",
"bootstrap-vue": "^2.0.0-rc.9",
"compression": "^1.7.2",
"cookie-session": "^1.2.0",
"bootstrap-vue": "^2.0.0-rc.18",
"compression": "^1.7.4",
"cookie-session": "^1.3.3",
"coupon-code": "^0.4.5",
"cross-env": "^5.2.0",
"css-loader": "^0.28.11",
"csv-stringify": "^4.3.1",
"csv-stringify": "^5.1.0",
"cwait": "^1.1.1",
"domain-middleware": "~0.1.0",
"express": "^4.16.3",
@@ -46,30 +48,30 @@
"got": "^9.0.0",
"gulp": "^4.0.0",
"gulp-babel": "^7.0.1",
"gulp-imagemin": "^5.0.3",
"gulp-imagemin": "^6.0.0",
"gulp-nodemon": "^2.4.1",
"gulp.spritesmith": "^6.9.0",
"habitica-markdown": "^1.3.0",
"hellojs": "^1.15.1",
"hellojs": "^1.18.1",
"html-webpack-plugin": "^3.2.0",
"image-size": "^0.6.2",
"in-app-purchase": "^1.10.2",
"image-size": "^0.7.0",
"in-app-purchase": "^1.11.3",
"intro.js": "^2.9.3",
"jquery": ">=3.0.0",
"js2xmlparser": "^3.0.0",
"js2xmlparser": "^4.0.0",
"lodash": "^4.17.10",
"merge-stream": "^1.0.0",
"merge-stream": "^2.0.0",
"method-override": "^3.0.0",
"moment": "^2.22.1",
"moment-recur": "^1.0.7",
"mongoose": "^5.3.4",
"mongoose": "^5.4.19",
"morgan": "^1.7.0",
"nconf": "^0.10.0",
"node-gcm": "^1.0.2",
"node-sass": "^4.9.0",
"nodemailer": "^4.6.4",
"ora": "^3.0.0",
"pageres": "^4.1.1",
"nodemailer": "^6.0.0",
"ora": "^3.2.0",
"pageres": "^5.1.0",
"passport": "^0.4.0",
"passport-facebook": "^2.0.0",
"passport-google-oauth20": "1.0.0",
@@ -83,12 +85,12 @@
"sass-loader": "^7.0.3",
"shelljs": "^0.8.2",
"short-uuid": "^3.0.0",
"smartbanner.js": "^1.9.1",
"smartbanner.js": "^1.11.0",
"stripe": "^5.9.0",
"superagent": "^4.0.0",
"superagent": "^5.0.2",
"svg-inline-loader": "^0.8.0",
"svg-url-loader": "^2.3.2",
"svgo": "^1.0.5",
"svgo": "^1.2.0",
"svgo-loader": "^2.1.0",
"universal-analytics": "^0.4.17",
"update": "^0.7.4",
@@ -96,15 +98,15 @@
"url-loader": "^1.0.0",
"useragent": "^2.1.9",
"uuid": "^3.0.1",
"validator": "^10.5.0",
"validator": "^11.0.0",
"vinyl-buffer": "^1.0.1",
"vue": "^2.5.16",
"vue": "^2.6.10",
"vue-loader": "^14.2.2",
"vue-mugen-scroll": "^0.2.1",
"vue-router": "^3.0.0",
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.5.16",
"vuedraggable": "^2.15.0",
"vue-template-compiler": "^2.6.10",
"vuedraggable": "^2.20.0",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"webpack": "^3.12.0",
"webpack-merge": "^4.1.3",
@@ -144,15 +146,15 @@
"apidoc": "gulp apidoc"
},
"devDependencies": {
"@vue/test-utils": "^1.0.0-beta.19",
"@vue/test-utils": "^1.0.0-beta.29",
"babel-plugin-istanbul": "^4.1.6",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chalk": "^2.4.1",
"chromedriver": "^2.40.0",
"chromedriver": "^73.0.0",
"connect-history-api-fallback": "^1.1.0",
"coveralls": "^3.0.1",
"coveralls": "^3.0.3",
"cross-spawn": "^6.0.5",
"eslint": "^4.19.1",
"eslint-config-habitrpg": "^4.0.0",
@@ -164,7 +166,7 @@
"expect.js": "^0.3.1",
"http-proxy-middleware": "^0.19.0",
"istanbul": "^1.1.0-alpha.1",
"karma": "^3.1.3",
"karma": "^4.0.1",
"karma-babel-preprocessor": "^7.0.0",
"karma-chai-plugins": "^0.9.0",
"karma-chrome-launcher": "^2.2.0",
@@ -179,11 +181,11 @@
"lcov-result-merger": "^3.0.0",
"mocha": "^5.1.1",
"monk": "^6.0.6",
"nightwatch": "^0.9.21",
"puppeteer": "^1.5.0",
"nightwatch": "^1.0.16",
"puppeteer": "^1.14.0",
"require-again": "^2.0.0",
"selenium-server": "^3.12.0",
"sinon": "^6.3.5",
"sinon": "^7.2.4",
"sinon-chai": "^3.0.0",
"sinon-stub-promise": "^4.0.0",
"webpack-bundle-analyzer": "^2.12.0",

View File

@@ -27,12 +27,13 @@ async function _deleteAmplitudeData (userId, email) {
if (response) console.log(`${response.status} ${response.statusText}`);
}
async function _deleteHabiticaData (user) {
async function _deleteHabiticaData (user, email) {
await User.update(
{_id: user._id},
{$set: {
'auth.local.passwordHashMethod': 'bcrypt',
'auth.local.email': email,
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
'auth.local.passwordHashMethod': 'bcrypt',
}}
);
const response = await axios.delete(
@@ -52,7 +53,7 @@ async function _deleteHabiticaData (user) {
if (response) {
console.log(`${response.status} ${response.statusText}`);
if (response.status === 200) console.log(`${user._id} removed. Last login: ${user.auth.timestamps.loggedin}`);
if (response.status === 200) console.log(`${user._id} (${email}) removed. Last login: ${user.auth.timestamps.loggedin}`);
}
}
@@ -75,7 +76,7 @@ async function _processEmailAddress (email) {
} else {
for (const user of users) {
await _deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
await _deleteHabiticaData(user); // eslint-disable-line no-await-in-loop
await _deleteHabiticaData(user, email); // eslint-disable-line no-await-in-loop
}
}
}

View File

@@ -1 +1 @@
For information about writing and running tests, see [Using Your Local Install to Modify Habitica's Website and API](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API).
For information about writing and running tests, see [Using Your Local Install to Modify Habitica's Website and API](http://habitica.fandom.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API).

View File

@@ -335,14 +335,13 @@ describe('analyticsService', () => {
let data, itemSpy;
beforeEach(() => {
Visitor.prototype.event.yields();
itemSpy = sandbox.stub().returnsThis();
Visitor.prototype.event.returns({
send: sandbox.stub(),
});
Visitor.prototype.transaction.returns({
item: itemSpy,
send: sandbox.stub().returnsThis(),
send: sandbox.stub().yields(),
});
data = {

View File

@@ -5,7 +5,9 @@ import {
BadRequest,
InternalServerError,
NotFound,
NotificationNotFound,
} from '../../../../website/server/libs/errors';
import i18n from '../../../../website/common/script/i18n';
describe('Custom Errors', () => {
describe('CustomError', () => {
@@ -66,6 +68,23 @@ describe('Custom Errors', () => {
expect(notAuthorizedError.message).to.eql('Custom Error Message');
});
describe('NotificationNotFound', () => {
it('is an instance of NotFound', () => {
const notificationNotFoundErr = new NotificationNotFound();
expect(notificationNotFoundErr).to.be.an.instanceOf(NotFound);
});
it('it returns an http code of 404', () => {
const notificationNotFoundErr = new NotificationNotFound();
expect(notificationNotFoundErr.httpCode).to.eql(404);
});
it('returns a standard message', () => {
const notificationNotFoundErr = new NotificationNotFound();
expect(notificationNotFoundErr.message).to.eql(i18n.t('messageNotificationNotFound'));
});
});
});
describe('BadRequest', () => {

View File

@@ -0,0 +1,113 @@
/* eslint-disable camelcase */
import {
validateItemPath,
getDefaultOwnedGear,
castItemVal,
} from '../../../../../website/server/libs/items/utils';
describe('Items Utils', () => {
describe('getDefaultOwnedGear', () => {
it('clones the result object', () => {
const res1 = getDefaultOwnedGear();
res1.extraProperty = true;
const res2 = getDefaultOwnedGear();
expect(res2).not.to.have.property('extraProperty');
});
});
describe('validateItemPath', () => {
it('returns false if not an item path', () => {
expect(validateItemPath('notitems.gear.owned.item')).to.equal(false);
});
it('returns true if a valid schema path', () => {
expect(validateItemPath('items.gear.equipped.weapon')).to.equal(true);
expect(validateItemPath('items.currentPet')).to.equal(true);
expect(validateItemPath('items.special.snowball')).to.equal(true);
});
it('works with owned gear paths', () => {
expect(validateItemPath('items.gear.owned.head_armoire_crownOfHearts')).to.equal(true);
expect(validateItemPath('items.gear.owned.head_invalid')).to.equal(false);
});
it('works with pets paths', () => {
expect(validateItemPath('items.pets.Wolf-CottonCandyPink')).to.equal(true);
expect(validateItemPath('items.pets.Wolf-Invalid')).to.equal(false);
});
it('works with eggs paths', () => {
expect(validateItemPath('items.eggs.LionCub')).to.equal(true);
expect(validateItemPath('items.eggs.Armadillo')).to.equal(true);
expect(validateItemPath('items.eggs.NotAnArmadillo')).to.equal(false);
});
it('works with hatching potions paths', () => {
expect(validateItemPath('items.hatchingPotions.Base')).to.equal(true);
expect(validateItemPath('items.hatchingPotions.StarryNight')).to.equal(true);
expect(validateItemPath('items.hatchingPotions.Invalid')).to.equal(false);
});
it('works with food paths', () => {
expect(validateItemPath('items.food.Cake_Base')).to.equal(true);
expect(validateItemPath('items.food.Cake_Invalid')).to.equal(false);
});
it('works with mounts paths', () => {
expect(validateItemPath('items.mounts.Cactus-Base')).to.equal(true);
expect(validateItemPath('items.mounts.Aether-Invisible')).to.equal(true);
expect(validateItemPath('items.mounts.Aether-Invalid')).to.equal(false);
});
it('works with quests paths', () => {
expect(validateItemPath('items.quests.atom3')).to.equal(true);
expect(validateItemPath('items.quests.invalid')).to.equal(false);
});
});
describe('castItemVal', () => {
it('returns the item val untouched if not an item path', () => {
expect(castItemVal('notitems.gear.owned.item', 'a string')).to.equal('a string');
});
it('returns the item val untouched if an unsupported path', () => {
expect(castItemVal('items.gear.equipped.weapon', 'a string')).to.equal('a string');
expect(castItemVal('items.currentPet', 'a string')).to.equal('a string');
expect(castItemVal('items.special.snowball', 'a string')).to.equal('a string');
});
it('converts values for pets paths to numbers', () => {
expect(castItemVal('items.pets.Wolf-CottonCandyPink', '5')).to.equal(5);
expect(castItemVal('items.pets.Wolf-Invalid', '5')).to.equal(5);
});
it('converts values for eggs paths to numbers', () => {
expect(castItemVal('items.eggs.LionCub', '5')).to.equal(5);
expect(castItemVal('items.eggs.Armadillo', '5')).to.equal(5);
expect(castItemVal('items.eggs.NotAnArmadillo', '5')).to.equal(5);
});
it('converts values for hatching potions paths to numbers', () => {
expect(castItemVal('items.hatchingPotions.Base', '5')).to.equal(5);
expect(castItemVal('items.hatchingPotions.StarryNight', '5')).to.equal(5);
expect(castItemVal('items.hatchingPotions.Invalid', '5')).to.equal(5);
});
it('converts values for food paths to numbers', () => {
expect(castItemVal('items.food.Cake_Base', '5')).to.equal(5);
expect(castItemVal('items.food.Cake_Invalid', '5')).to.equal(5);
});
it('converts values for mounts paths to numbers', () => {
expect(castItemVal('items.mounts.Cactus-Base', '5')).to.equal(5);
expect(castItemVal('items.mounts.Aether-Invisible', '5')).to.equal(5);
expect(castItemVal('items.mounts.Aether-Invalid', '5')).to.equal(5);
});
it('converts values for quests paths to numbers', () => {
expect(castItemVal('items.quests.atom3', '5')).to.equal(5);
expect(castItemVal('items.quests.invalid', '5')).to.equal(5);
});
});
});

View File

@@ -6,6 +6,7 @@ import iap from '../../../../../website/server/libs/inAppPurchases';
import {model as User} from '../../../../../website/server/models/user';
import common from '../../../../../website/common';
import moment from 'moment';
import {mockFindById, restoreFindById} from '../../../../helpers/mongoose.helper';
const i18n = common.i18n;
@@ -49,7 +50,7 @@ describe('Apple Payments', () => {
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(false);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -61,7 +62,7 @@ describe('Apple Payments', () => {
iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData').returns([]);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -71,7 +72,7 @@ describe('Apple Payments', () => {
it('errors if the user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').resolves(false);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -89,7 +90,7 @@ describe('Apple Payments', () => {
transactionId: token,
}]);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -131,7 +132,7 @@ describe('Apple Payments', () => {
}]);
sinon.stub(user, 'canGetGems').resolves(true);
await applePayments.verifyGemPurchase(user, receipt, headers);
await applePayments.verifyGemPurchase({user, receipt, headers});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
@@ -151,6 +152,38 @@ describe('Apple Payments', () => {
user.canGetGems.restore();
});
});
it('gifts gems', async () => {
const receivingUser = new User();
await receivingUser.save();
mockFindById(receivingUser);
iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
.returns([{productId: gemsCanPurchase[0].productId,
transactionId: token,
}]);
const gift = {uuid: receivingUser._id};
await applePayments.verifyGemPurchase({user, gift, receipt, headers});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({});
expect(iapGetPurchaseDataStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user: receivingUser,
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
amount: gemsCanPurchase[0].amount,
headers,
});
restoreFindById();
});
});
describe('subscribe', () => {

View File

@@ -6,6 +6,7 @@ import iap from '../../../../../website/server/libs/inAppPurchases';
import {model as User} from '../../../../../website/server/models/user';
import common from '../../../../../website/common';
import moment from 'moment';
import {mockFindById, restoreFindById} from '../../../../helpers/mongoose.helper';
const i18n = common.i18n;
@@ -44,7 +45,7 @@ describe('Google Payments', () => {
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(false);
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -55,7 +56,7 @@ describe('Google Payments', () => {
it('should throw an error if productId is invalid', async () => {
receipt = `{"token": "${token}", "productId": "invalid"}`;
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -66,7 +67,7 @@ describe('Google Payments', () => {
it('should throw an error if user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').resolves(false);
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -78,7 +79,7 @@ describe('Google Payments', () => {
it('purchases gems', async () => {
sinon.stub(user, 'canGetGems').resolves(true);
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
await googlePayments.verifyGemPurchase({user, receipt, signature, headers});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
@@ -99,6 +100,34 @@ describe('Google Payments', () => {
expect(user.canGetGems).to.be.calledOnce;
user.canGetGems.restore();
});
it('gifts gems', async () => {
const receivingUser = new User();
await receivingUser.save();
mockFindById(receivingUser);
const gift = {uuid: receivingUser._id};
await googlePayments.verifyGemPurchase({user, gift, receipt, signature, headers});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
data: receipt,
signature,
});
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({});
expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user: receivingUser,
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
amount: 5.25,
headers,
});
restoreFindById();
});
});
describe('subscribe', () => {

View File

@@ -32,6 +32,7 @@ describe('slack', () => {
},
message: {
id: 'chat-id',
username: 'author',
user: 'Author',
uuid: 'author-id',
text: 'some text',
@@ -50,11 +51,11 @@ describe('slack', () => {
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: 'flagger (flagger-id; language: flagger-lang) flagged a message',
text: 'flagger (flagger-id; language: flagger-lang) flagged a group message',
attachments: [{
fallback: 'Flag Message',
color: 'danger',
author_name: `Author - author@example.com - author-id\n${timestamp}`,
author_name: `@author Author (author@example.com; author-id)\n${timestamp}`,
title: 'Flag in Some group - (private guild)',
title_link: undefined,
text: 'some text',

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

@@ -73,6 +73,56 @@ describe('redirects middleware', () => {
expect(res.redirect).to.have.not.been.called;
});
it('does not redirect if passed skip ssl request param is passed with corrrect key', () => {
let nconfStub = sandbox.stub(nconf, 'get');
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
nconfStub.withArgs('IS_PROD').returns(true);
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key');
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
req.originalUrl = '/static/front';
req.query.skipSSLCheck = 'test-key';
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
attachRedirects.forceSSL(req, res, next);
expect(res.redirect).to.have.not.been.called;
});
it('does redirect if skip ssl request param is passed with incorrrect key', () => {
let nconfStub = sandbox.stub(nconf, 'get');
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
nconfStub.withArgs('IS_PROD').returns(true);
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key');
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
req.originalUrl = '/static/front?skipSSLCheck=INVALID';
req.query.skipSSLCheck = 'INVALID';
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
attachRedirects.forceSSL(req, res, next);
expect(res.redirect).to.be.calledOnce;
expect(res.redirect).to.be.calledWith('https://habitica.com/static/front?skipSSLCheck=INVALID');
});
it('does redirect if skip ssl check key is not set', () => {
let nconfStub = sandbox.stub(nconf, 'get');
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
nconfStub.withArgs('IS_PROD').returns(true);
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns(null);
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
req.originalUrl = '/static/front';
req.query.skipSSLCheck = 'INVALID';
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
attachRedirects.forceSSL(req, res, next);
expect(res.redirect).to.be.calledOnce;
expect(res.redirect).to.be.calledWith('https://habitica.com/static/front');
});
});
context('forceHabitica', () => {

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,
@@ -32,8 +32,19 @@ describe('Group Model', () => {
privacy: 'private',
});
let _progress = {
up: 10,
down: 8,
collectedItems: 5,
};
questLeader = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Quest Leader' },
items: {
quests: {
@@ -45,20 +56,40 @@ describe('Group Model', () => {
party.leader = questLeader._id;
participatingMember = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Participating Member' },
});
sleepingParticipatingMember = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Sleeping Participating Member' },
preferences: { sleep: true },
});
nonParticipatingMember = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Non-Participating Member' },
});
undecidedMember = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Undecided Member' },
});
@@ -240,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 () => {
@@ -313,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 () => {
@@ -356,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);
});
@@ -406,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);
@@ -457,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 () => {
@@ -468,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 () => {
@@ -485,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 () => {
@@ -504,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 () => {
@@ -516,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 () => {
@@ -687,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', () => {
@@ -976,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;
@@ -1006,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);
@@ -1021,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({
@@ -1035,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({
@@ -1045,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({
@@ -1057,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;
});
@@ -1065,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;
});
@@ -1163,16 +1497,17 @@ describe('Group Model', () => {
expect(party.quest.members).to.eql(expectedQuestMembers);
});
it('applies updates to user object directly if user is participating', async () => {
it('applies updates to user object directly if user is participating (without resetting progress, except progress.down)', async () => {
await party.startQuest(participatingMember);
expect(participatingMember.party.quest.key).to.eql('whale');
expect(participatingMember.party.quest.progress.up).to.eql(10);
expect(participatingMember.party.quest.progress.down).to.eql(0);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(0);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(participatingMember.party.quest.completed).to.eql(null);
});
it('applies updates to other participating members', async () => {
it('applies updates to other participating members (without resetting progress, except progress.down)', async () => {
await party.startQuest(nonParticipatingMember);
questLeader = await User.findById(questLeader._id);
@@ -1180,18 +1515,21 @@ describe('Group Model', () => {
sleepingParticipatingMember = await User.findById(sleepingParticipatingMember._id);
expect(participatingMember.party.quest.key).to.eql('whale');
expect(participatingMember.party.quest.progress.up).to.eql(10);
expect(participatingMember.party.quest.progress.down).to.eql(0);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(0);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(participatingMember.party.quest.completed).to.eql(null);
expect(sleepingParticipatingMember.party.quest.key).to.eql('whale');
expect(sleepingParticipatingMember.party.quest.progress.up).to.eql(10);
expect(sleepingParticipatingMember.party.quest.progress.down).to.eql(0);
expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(0);
expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(sleepingParticipatingMember.party.quest.completed).to.eql(null);
expect(questLeader.party.quest.key).to.eql('whale');
expect(questLeader.party.quest.progress.up).to.eql(10);
expect(questLeader.party.quest.progress.down).to.eql(0);
expect(questLeader.party.quest.progress.collectedItems).to.eql(0);
expect(questLeader.party.quest.progress.collectedItems).to.eql(5);
expect(questLeader.party.quest.completed).to.eql(null);
});
@@ -1202,6 +1540,9 @@ describe('Group Model', () => {
undecidedMember = await User.findById(undecidedMember._id);
expect(nonParticipatingMember.party.quest.key).to.not.eql('whale');
expect(nonParticipatingMember.party.quest.progress.up).to.eql(10);
expect(nonParticipatingMember.party.quest.progress.down).to.eql(8);
expect(nonParticipatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(undecidedMember.party.quest.key).to.not.eql('whale');
});
@@ -1369,8 +1710,9 @@ describe('Group Model', () => {
let userQuest = participatingMember.party.quest;
expect(userQuest.key).to.eql('whale');
expect(userQuest.progress.up).to.eql(10);
expect(userQuest.progress.down).to.eql(0);
expect(userQuest.progress.collectedItems).to.eql(0);
expect(userQuest.progress.collectedItems).to.eql(5);
expect(userQuest.completed).to.eql(null);
});
@@ -1392,7 +1734,7 @@ describe('Group Model', () => {
let quest;
beforeEach(() => {
quest = questScrolls.whale;
quest = questScrolls.armadillo;
party.quest.key = quest.key;
party.quest.active = false;
party.quest.leader = questLeader._id;
@@ -1540,6 +1882,36 @@ describe('Group Model', () => {
expect(updatedSleepingParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
});
it('gives out other pet-related quest achievements', async () => {
quest = questScrolls.rock;
party.quest.key = quest.key;
questLeader.achievements.quests = {
mayhemMistiflying1: 1,
yarn: 1,
mayhemMistiflying2: 1,
egg: 1,
mayhemMistiflying3: 1,
slime: 2,
};
await questLeader.save();
await party.finishQuest(quest);
let [
updatedLeader,
updatedParticipatingMember,
updatedSleepingParticipatingMember,
] = await Promise.all([
User.findById(questLeader._id).exec(),
User.findById(participatingMember._id).exec(),
User.findById(sleepingParticipatingMember._id).exec(),
]);
expect(updatedLeader.achievements.mindOverMatter).to.eql(true);
expect(updatedParticipatingMember.achievements.mindOverMatter).to.not.eql(true);
expect(updatedSleepingParticipatingMember.achievements.mindOverMatter).to.not.eql(true);
});
it('gives xp and gold', async () => {
await party.finishQuest(quest);
@@ -1670,16 +2042,23 @@ describe('Group Model', () => {
});
});
it('sets user quest object to a clean state', async () => {
it('updates participating members quest object to a clean state (except for progress)', async () => {
await party.finishQuest(quest);
let updatedLeader = await User.findById(questLeader._id);
questLeader = await User.findById(questLeader._id);
participatingMember = await User.findById(participatingMember._id);
expect(updatedLeader.party.quest.completed).to.eql('whale');
expect(updatedLeader.party.quest.progress.up).to.eql(0);
expect(updatedLeader.party.quest.progress.down).to.eql(0);
expect(updatedLeader.party.quest.progress.collectedItems).to.eql(0);
expect(updatedLeader.party.quest.RSVPNeeded).to.eql(false);
expect(questLeader.party.quest.completed).to.eql('armadillo');
expect(questLeader.party.quest.progress.up).to.eql(10);
expect(questLeader.party.quest.progress.down).to.eql(8);
expect(questLeader.party.quest.progress.collectedItems).to.eql(5);
expect(questLeader.party.quest.RSVPNeeded).to.eql(false);
expect(participatingMember.party.quest.completed).to.eql('armadillo');
expect(participatingMember.party.quest.progress.up).to.eql(10);
expect(participatingMember.party.quest.progress.down).to.eql(8);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(participatingMember.party.quest.RSVPNeeded).to.eql(false);
});
});
@@ -1882,7 +2261,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

@@ -65,11 +65,11 @@ describe('GET /challenges/:challengeId/export/csv', () => {
const sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
const splitRes = res.split('\n');
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[0]).to.equal('UUID,Display Name,Username,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},${sortedMembers[0].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},${sortedMembers[1].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},${sortedMembers[2].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},${sortedMembers[3].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[5]).to.equal('');
});
@@ -78,10 +78,10 @@ describe('GET /challenges/:challengeId/export/csv', () => {
const res = await members[1].get(`/challenges/${challenge._id}/export/csv`);
const sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id');
const splitRes = res.split('\n');
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[0]).to.equal('UUID,Display Name,Username,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},${sortedMembers[0].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},${sortedMembers[1].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},${sortedMembers[2].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[4]).to.equal('');
});
});

View File

@@ -171,7 +171,7 @@ describe('GET challenges/user', () => {
});
});
it('should return not return challenges in user groups if we send member true param', async () => {
it('should not return challenges in user groups if we send member true param', async () => {
let challenges = await member.get(`/challenges/user?member=${true}`);
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
@@ -214,6 +214,28 @@ describe('GET challenges/user', () => {
let foundChallenge = _.find(challenges, { _id: privateChallenge._id });
expect(foundChallenge).to.not.exist;
});
it('should not return challenges user doesn\'t have access to, even with query parameters', async () => {
let { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestPrivateGuild',
summary: 'summary for TestPrivateGuild',
type: 'guild',
privacy: 'private',
},
});
let privateChallenge = await generateChallenge(groupLeader, group, {categories: [{
name: 'academics',
slug: 'academics',
}]});
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
let challenges = await nonMember.get('/challenges/user?categories=academics&owned=not_owned');
let foundChallenge = _.find(challenges, { _id: privateChallenge._id });
expect(foundChallenge).to.not.exist;
});
});
context('official challenge is present', () => {

View File

@@ -56,11 +56,11 @@ describe('PUT /challenges/:challengeId', () => {
tasksOrder: 'new order',
official: true,
shortName: 'new short name',
leader: member._id,
// applied
name: 'New Challenge Name',
description: 'New challenge description.',
leader: member._id,
});
expect(res.prize).to.equal(0);
@@ -76,12 +76,12 @@ describe('PUT /challenges/:challengeId', () => {
expect(res.shortName).not.to.equal('new short name');
expect(res.leader).to.eql({
_id: member._id,
id: member._id,
profile: {name: member.profile.name},
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
auth: {
local: {
username: member.auth.local.username,
username: user.auth.local.username,
},
},
flags: {

View File

@@ -63,11 +63,11 @@ describe('POST /chat/:chatId/flag', () => {
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: `${user.profile.name} (${user.id}; language: en) flagged a message`,
text: `${user.profile.name} (${user.id}; language: en) flagged a group message`,
attachments: [{
fallback: 'Flag Message',
color: 'danger',
author_name: `${anotherUser.profile.name} - ${anotherUser.auth.local.email} - ${anotherUser._id}\n${timestamp}`,
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
title: 'Flag in Test Guild',
title_link: `${BASE_URL}/groups/guild/${group._id}`,
text: TEST_MESSAGE,
@@ -98,11 +98,11 @@ describe('POST /chat/:chatId/flag', () => {
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a message`,
text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a group message`,
attachments: [{
fallback: 'Flag Message',
color: 'danger',
author_name: `${newUser.profile.name} - ${newUser.auth.local.email} - ${newUser._id}\n${timestamp}`,
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
title: 'Flag in Test Guild',
title_link: `${BASE_URL}/groups/guild/${group._id}`,
text: TEST_MESSAGE,

View File

@@ -257,7 +257,7 @@ describe('POST /chat', () => {
attachments: [{
fallback: 'Slur Message',
color: 'danger',
author_name: `${user.profile.name} - ${user.auth.local.email} - ${user._id}`,
author_name: `@${user.auth.local.username} ${user.profile.name} (${user.auth.local.email}; ${user._id})`,
title: 'Slur in Test Guild',
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
text: testSlurMessage,
@@ -310,7 +310,7 @@ describe('POST /chat', () => {
attachments: [{
fallback: 'Slur Message',
color: 'danger',
author_name: `${members[0].profile.name} - ${members[0].auth.local.email} - ${members[0]._id}`,
author_name: `@${members[0].auth.local.username} ${members[0].profile.name} (${members[0].auth.local.email}; ${members[0]._id})`,
title: 'Slur in Party - (private party)',
title_link: undefined,
text: testSlurMessage,

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

@@ -25,9 +25,9 @@ describe('GET /heroes/:heroId', () => {
it('validates req.params.heroId', async () => {
await expect(user.get('/hall/heroes/invalidUUID')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
code: 404,
error: 'NotFound',
message: t('userWithIDNotFound', {userId: 'invalidUUID'}),
});
});
@@ -40,7 +40,7 @@ describe('GET /heroes/:heroId', () => {
});
});
it('returns only necessary hero data', async () => {
it('returns only necessary hero data given user id', async () => {
let hero = await generateUser({
contributor: {tier: 23},
});
@@ -53,4 +53,24 @@ describe('GET /heroes/:heroId', () => {
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
expect(heroRes.profile).to.have.all.keys(['name']);
});
it('returns only necessary hero data given username', async () => {
let hero = await generateUser({
contributor: {tier: 23},
});
let heroRes = await user.get(`/hall/heroes/${hero.auth.local.username}`);
expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys
'_id', 'id', 'balance', 'profile', 'purchased',
'contributor', 'auth', 'items',
]);
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
expect(heroRes.profile).to.have.all.keys(['name']);
});
it('returns correct hero using search with difference case', async () => {
await generateUser({}, { username: 'TestUpperCaseName123' });
let heroRes = await user.get('/hall/heroes/TestuPPerCasEName123');
expect(heroRes.auth.local.username).to.equal('TestUpperCaseName123');
});
});

View File

@@ -27,8 +27,6 @@ describe('GET /inbox/messages', () => {
toUserId: user.id,
message: 'fourth',
});
await user.sync();
});
it('returns the user inbox messages as an array of ordered messages (from most to least recent)', async () => {
@@ -45,4 +43,27 @@ describe('GET /inbox/messages', () => {
expect(messages[2].text).to.equal('second');
expect(messages[3].text).to.equal('first');
});
it('returns four messages when using page-query ', async () => {
const promises = [];
for (let i = 0; i < 10; i++) {
promises.push(user.post('/members/send-private-message', {
toUserId: user.id,
message: 'fourth',
}));
}
await Promise.all(promises);
const messages = await user.get('/inbox/messages?page=1');
expect(messages.length).to.equal(4);
});
it('returns only the messages of one conversation', async () => {
const messages = await user.get(`/inbox/messages?conversation=${otherUser.id}`);
expect(messages.length).to.equal(3);
});
});

View File

@@ -16,7 +16,7 @@ describe('POST /notifications/:notificationId/read', () => {
await expect(user.post(`/notifications/${dummyId}/read`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
error: 'NotificationNotFound',
message: t('messageNotificationNotFound'),
});
});

View File

@@ -16,7 +16,7 @@ describe('POST /notifications/:notificationId/see', () => {
await expect(user.post(`/notifications/${dummyId}/see`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
error: 'NotificationNotFound',
message: t('messageNotificationNotFound'),
});
});

View File

@@ -18,7 +18,7 @@ describe('POST /notifications/read', () => {
notificationIds: [dummyId],
})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
error: 'NotificationNotFound',
message: t('messageNotificationNotFound'),
});
});

View File

@@ -18,7 +18,7 @@ describe('POST /notifications/see', () => {
notificationIds: [dummyId],
})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
error: 'NotificationNotFound',
message: t('messageNotificationNotFound'),
});
});

View File

@@ -0,0 +1,67 @@
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import applePayments from '../../../../../../website/server/libs/payments/apple';
describe('payments : apple #norenewsubscribe', () => {
let endpoint = '/iap/ios/norenew-subscribe';
let sku = 'com.habitrpg.ios.habitica.subscription.3month';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies sub key', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingSubscriptionCode'),
});
});
it('verifies receipt existence', async () => {
await expect(user.post(endpoint, {
sku,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingReceipt'),
});
});
describe('success', () => {
let subscribeStub;
beforeEach(async () => {
subscribeStub = sinon.stub(applePayments, 'noRenewSubscribe').resolves({});
});
afterEach(() => {
applePayments.noRenewSubscribe.restore();
});
it('makes a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint, {
sku,
transaction: {receipt: 'receipt'},
gift: {
uuid: '1',
},
});
expect(subscribeStub).to.be.calledOnce;
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
expect(subscribeStub.args[0][0].sku).to.eql(sku);
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});

View File

@@ -1,4 +1,4 @@
import {generateUser} from '../../../../../helpers/api-integration/v3';
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import applePayments from '../../../../../../website/server/libs/payments/apple';
describe('payments : apple #verify', () => {
@@ -9,6 +9,14 @@ describe('payments : apple #verify', () => {
user = await generateUser();
});
it('verifies receipt existence', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingReceipt'),
});
});
describe('success', () => {
let verifyStub;
@@ -31,10 +39,31 @@ describe('payments : apple #verify', () => {
}});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0]._id).to.eql(user._id);
expect(verifyStub.args[0][1]).to.eql('receipt');
expect(verifyStub.args[0][2]['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][2]['x-api-user']).to.eql(user._id);
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
it('gifts a purchase', async () => {
user = await generateUser({
balance: 2,
});
await user.post(endpoint, {
transaction: {
receipt: 'receipt',
},
gift: {
uuid: '1',
}});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
expect(verifyStub.args[0][0].gift.uuid).to.eql('1');
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});

View File

@@ -0,0 +1,97 @@
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import googlePayments from '../../../../../../website/server/libs/payments/google';
describe('payments : google #norenewsubscribe', () => {
let endpoint = '/iap/android/norenew-subscribe';
let sku = 'com.habitrpg.android.habitica.subscription.3month';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies sub key', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingSubscriptionCode'),
});
});
it('verifies receipt existence', async () => {
await expect(user.post(endpoint, {
sku,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingReceipt'),
});
});
describe('success', () => {
let subscribeStub;
beforeEach(async () => {
subscribeStub = sinon.stub(googlePayments, 'noRenewSubscribe').resolves({});
});
afterEach(() => {
googlePayments.noRenewSubscribe.restore();
});
it('makes a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint, {
sku,
transaction: {
receipt: 'receipt',
signature: 'signature',
},
});
expect(subscribeStub).to.be.calledOnce;
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
expect(subscribeStub.args[0][0].sku).to.eql(sku);
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
expect(subscribeStub.args[0][0].signature).to.eql('signature');
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
it('gifts a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint, {
sku,
transaction: {
receipt: 'receipt',
signature: 'signature',
},
gift: {
uuid: '1',
},
});
expect(subscribeStub).to.be.calledOnce;
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
expect(subscribeStub.args[0][0].sku).to.eql(sku);
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
expect(subscribeStub.args[0][0].signature).to.eql('signature');
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});

View File

@@ -1,4 +1,4 @@
import {generateUser} from '../../../../../helpers/api-integration/v3';
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import googlePayments from '../../../../../../website/server/libs/payments/google';
describe('payments : google #verify', () => {
@@ -9,6 +9,14 @@ describe('payments : google #verify', () => {
user = await generateUser();
});
it('verifies receipt existence', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingReceipt'),
});
});
describe('success', () => {
let verifyStub;
@@ -30,11 +38,30 @@ describe('payments : google #verify', () => {
});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0]._id).to.eql(user._id);
expect(verifyStub.args[0][1]).to.eql('receipt');
expect(verifyStub.args[0][2]).to.eql('signature');
expect(verifyStub.args[0][3]['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][3]['x-api-user']).to.eql(user._id);
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
expect(verifyStub.args[0][0].signature).to.eql('signature');
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
it('gifts a purchase', async () => {
user = await generateUser({
balance: 2,
});
await user.post(endpoint, {
transaction: {receipt: 'receipt', signature: 'signature'},
gift: {uuid: '1'},
});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
expect(verifyStub.args[0][0].signature).to.eql('signature');
expect(verifyStub.args[0][0].gift.uuid).to.eql('1');
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});

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

@@ -4,6 +4,7 @@ import {
generateUser,
} from '../../../../helpers/api-integration/v3';
import { v4 as generateUUID } from 'uuid';
import { model as Group } from '../../../../../website/server/models/group';
describe('POST /groups/:groupId/quests/cancel', () => {
let questingGroup;
@@ -99,6 +100,10 @@ describe('POST /groups/:groupId/quests/cancel', () => {
it('cancels a quest', async () => {
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
// partyMembers[1] hasn't accepted the invitation, because if he accepts, invitation phase ends.
// The cancel command can be done only in the invitation phase.
let stub = sandbox.spy(Group.prototype, 'sendChat');
let res = await leader.post(`/groups/${questingGroup._id}/quests/cancel`);
@@ -135,5 +140,16 @@ describe('POST /groups/:groupId/quests/cancel', () => {
},
members: {},
});
expect(Group.prototype.sendChat).to.be.calledOnce;
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

@@ -63,6 +63,38 @@ describe('Groups DELETE /tasks/:id', () => {
});
});
it('removes deleted taskʾs approval pending notifications from managers', async () => {
await user.post(`/groups/${guild._id}/add-manager`, {
managerId: member2._id,
});
await user.put(`/tasks/${task._id}/`, {
requiresApproval: true,
});
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.sync();
await member2.sync();
expect(user.notifications.length).to.equal(2);
expect(user.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
expect(member2.notifications.length).to.equal(2);
expect(member2.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
await member2.del(`/tasks/${task._id}`);
await user.sync();
await member2.sync();
expect(user.notifications.length).to.equal(1);
expect(member2.notifications.length).to.equal(1);
});
it('unlinks assigned user', async () => {
await user.del(`/tasks/${task._id}`);

View File

@@ -53,18 +53,29 @@ describe('POST /tasks/:id/approve/:userId', () => {
it('approves an assigned user', async () => {
await user.post(`/tasks/${task._id}/assign/${member._id}`);
await user.post(`/tasks/${task._id}/approve/${member._id}`);
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${task._id}/approve/${member._id}`);
await member.sync();
expect(member.notifications.length).to.equal(2);
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED');
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
expect(member.notifications[1].type).to.equal('SCORED_TASK');
expect(member.notifications.length).to.equal(3);
expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED');
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
expect(member.notifications[2].type).to.equal('SCORED_TASK');
expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
memberTasks = await member.get('/tasks/user');
syncedTask = find(memberTasks, findAssignedTask);
expect(syncedTask.group.approval.approved).to.be.true;
expect(syncedTask.group.approval.approvingUser).to.equal(user._id);
@@ -77,18 +88,28 @@ describe('POST /tasks/:id/approve/:userId', () => {
});
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
await member.sync();
expect(member.notifications.length).to.equal(2);
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED');
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
expect(member.notifications[1].type).to.equal('SCORED_TASK');
expect(member.notifications.length).to.equal(3);
expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED');
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
expect(member.notifications[2].type).to.equal('SCORED_TASK');
expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
memberTasks = await member.get('/tasks/user');
syncedTask = find(memberTasks, findAssignedTask);
expect(syncedTask.group.approval.approved).to.be.true;
expect(syncedTask.group.approval.approvingUser).to.equal(member2._id);
@@ -132,6 +153,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
});
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
await expect(user.post(`/tasks/${task._id}/approve/${member._id}`))
.to.eventually.be.rejected.and.to.eql({
@@ -141,6 +172,17 @@ describe('POST /tasks/:id/approve/:userId', () => {
});
});
it('prevents approving a task if it is not waiting for approval', async () => {
await user.post(`/tasks/${task._id}/assign/${member._id}`);
await expect(user.post(`/tasks/${task._id}/approve/${member._id}`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalWasNotRequested'),
});
});
it('completes master task when single-completion task is approved', async () => {
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
text: 'shared completion todo',
@@ -151,6 +193,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
let groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`);
@@ -172,6 +224,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
let member2Tasks = await member2.get('/tasks/user');
@@ -193,6 +255,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
let groupTasks = await user.get(`/tasks/group/${guild._id}`);
@@ -214,6 +286,25 @@ describe('POST /tasks/:id/approve/:userId', () => {
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
let member2Tasks = await member2.get('/tasks/user');
let member2SyncedTask = find(member2Tasks, findAssignedTask);
await expect(member2.post(`/tasks/${member2SyncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member2._id}`);

View File

@@ -51,7 +51,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
});
});
it('marks as task as needing more work', async () => {
it('marks a task as needing more work', async () => {
const initialNotifications = member.notifications.length;
await user.post(`/tasks/${task._id}/assign/${member._id}`);
@@ -77,7 +77,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
// Check that the notification is correct
expect(member.notifications.length).to.equal(initialNotifications + 1);
expect(member.notifications.length).to.equal(initialNotifications + 2);
const notification = member.notifications[member.notifications.length - 1];
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
@@ -131,7 +131,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
expect(syncedTask.group.approval.requested).to.equal(false);
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
expect(member.notifications.length).to.equal(initialNotifications + 1);
expect(member.notifications.length).to.equal(initialNotifications + 2);
const notification = member.notifications[member.notifications.length - 1];
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
@@ -167,6 +167,17 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
});
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
.to.eventually.be.rejected.and.to.eql({

View File

@@ -129,6 +129,13 @@ describe('POST /tasks/:id/score/:direction', () => {
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${task._id}/approve/${member._id}`);
await member.post(`/tasks/${syncedTask._id}/score/up`);

View File

@@ -113,6 +113,17 @@ describe('POST /tasks/:taskId/assign/:memberId', () => {
expect(syncedTask).to.exist;
});
it('sends a notification to assigned user', async () => {
await user.post(`/tasks/${task._id}/assign/${member._id}`);
await member.sync();
let groupTask = await user.get(`/tasks/group/${guild._id}`);
expect(member.notifications.length).to.equal(1);
expect(member.notifications[0].type).to.equal('GROUP_TASK_ASSIGNED');
expect(member.notifications[0].taskId).to.equal(groupTask._id);
});
it('assigns a task to multiple users', async () => {
await user.post(`/tasks/${task._id}/assign/${member._id}`);
await user.post(`/tasks/${task._id}/assign/${member2._id}`);

View File

@@ -86,6 +86,13 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => {
expect(syncedTask).to.not.exist;
});
it('removes task assignment notification from unassigned user', async () => {
await user.post(`/tasks/${task._id}/unassign/${member._id}`);
await member.sync();
expect(member.notifications.length).to.equal(0);
});
it('unassigns a user and only that user from a task', async () => {
await user.post(`/tasks/${task._id}/assign/${member2._id}`);

View File

@@ -110,4 +110,22 @@ describe('POST /user/auth/local/login', () => {
let isValidPassword = await bcryptCompare(textPassword, user.auth.local.hashed_password);
expect(isValidPassword).to.equal(true);
});
it('user uses social authentication and has no password', async () => {
await user.unset({
'auth.local.hashed_password': 1,
});
await user.sync();
expect(user.auth.local.hashed_password).to.be.undefined;
await expect(api.post(endpoint, {
username: user.auth.local.username,
password: 'any-password',
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('invalidLoginCredentialsLong'),
});
});
});

View File

@@ -0,0 +1,44 @@
import {
generateUser,
} from '../../../helpers/api-integration/v4';
describe('GET /inbox/conversations', () => {
let user;
let otherUser;
let thirdUser;
before(async () => {
[user, otherUser, thirdUser] = await Promise.all([generateUser(), generateUser(), generateUser()]);
await otherUser.post('/members/send-private-message', {
toUserId: user.id,
message: 'first',
});
await user.post('/members/send-private-message', {
toUserId: otherUser.id,
message: 'second',
});
await user.post('/members/send-private-message', {
toUserId: thirdUser.id,
message: 'third',
});
await otherUser.post('/members/send-private-message', {
toUserId: user.id,
message: 'fourth',
});
// message to yourself
await user.post('/members/send-private-message', {
toUserId: user.id,
message: 'fifth',
});
});
it('returns the conversations', async () => {
const result = await user.get('/inbox/conversations');
expect(result.length).to.be.equal(3);
expect(result[0].user).to.be.equal(user.profile.name);
expect(result[0].username).to.be.equal(user.auth.local.username);
});
});

View File

@@ -0,0 +1,74 @@
import {
generateUser,
translate as t,
} from '../../../helpers/api-integration/v4';
describe('POST /members/flag-private-message/:messageId', () => {
let userToSendMessage;
let messageToSend = 'Test Private Message';
beforeEach(async () => {
userToSendMessage = await generateUser();
});
it('Allows players to flag their own private message', async () => {
let receiver = await generateUser();
await userToSendMessage.post('/members/send-private-message', {
message: messageToSend,
toUserId: receiver._id,
});
let senderMessages = await userToSendMessage.get('/inbox/messages');
let sendersMessageInSendersInbox = _.find(senderMessages, (message) => {
return message.uuid === receiver._id && message.text === messageToSend;
});
expect(sendersMessageInSendersInbox).to.exist;
await expect(userToSendMessage.post(`/members/flag-private-message/${sendersMessageInSendersInbox.id}`)).to.eventually.be.ok;
});
it('Flags a private message', async () => {
let receiver = await generateUser();
await userToSendMessage.post('/members/send-private-message', {
message: messageToSend,
toUserId: receiver._id,
});
let receiversMessages = await receiver.get('/inbox/messages');
let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
return message.uuid === userToSendMessage._id && message.text === messageToSend;
});
expect(sendersMessageInReceiversInbox).to.exist;
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`)).to.eventually.be.ok;
});
it('Returns an error when user tries to flag a private message that is already flagged', async () => {
let receiver = await generateUser();
await userToSendMessage.post('/members/send-private-message', {
message: messageToSend,
toUserId: receiver._id,
});
let receiversMessages = await receiver.get('/inbox/messages');
let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
return message.uuid === userToSendMessage._id && message.text === messageToSend;
});
expect(sendersMessageInReceiversInbox).to.exist;
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`)).to.eventually.be.ok;
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('messageGroupChatFlagAlreadyReported'),
});
});
});

View File

@@ -0,0 +1,300 @@
import Avatar from 'client/components/avatar';
import Vue from 'vue';
import generateStore from 'client/store';
context('avatar.vue', () => {
let Constructr;
let vm;
beforeEach(() => {
Constructr = Vue.extend(Avatar);
vm = new Constructr({
propsData: {
member: {
stats: {
buffs: {},
},
preferences: {
hair: {},
},
items: {
gear: {
equipped: {},
},
},
},
},
}).$mount();
vm.$store = generateStore();
});
afterEach(() => {
vm.$destroy();
});
describe('hasClass', () => {
beforeEach(() => {
vm.member = {
stats: { lvl: 17 },
preferences: { disableClasses: true },
flags: { classSelected: false },
};
});
it('accurately reports class status', () => {
expect(vm.hasClass).to.equal(false);
vm.member.preferences.disableClasses = false;
vm.member.flags.classSelected = true;
expect(vm.hasClass).to.equal(true);
});
});
describe('isBuffed', () => {
beforeEach(() => {
vm.member = {
stats: {
buffs: {},
},
};
});
it('accurately reports if buffed', () => {
expect(vm.isBuffed).to.equal(undefined);
vm.member.stats.buffs = { str: 1 };
expect(vm.isBuffed).to.equal(1);
});
});
describe('paddingTop', () => {
beforeEach(() => {
vm.member = {
items: {},
};
});
it('defaults to 28px', () => {
vm.avatarOnly = true;
expect(vm.paddingTop).to.equal('28px');
});
it('is 24.5px if user has a pet', () => {
vm.member.items = {
currentPet: { name: 'Foo' },
};
expect(vm.paddingTop).to.equal('24.5px');
});
it('is 0px if user has a mount', () => {
vm.member.items = {
currentMount: { name: 'Bar' },
};
expect(vm.paddingTop).to.equal('0px');
});
it('can be overriden', () => {
vm.overrideTopPadding = '27px';
expect(vm.paddingTop).to.equal('27px');
});
});
describe('costumeClass', () => {
beforeEach(() => {
vm.member = {
preferences: {},
};
});
it('returns if showing equiped gear', () => {
expect(vm.costumeClass).to.equal('equipped');
});
it('returns if wearing a costume', () => {
vm.member.preferences = { costume: true };
expect(vm.costumeClass).to.equal('costume');
});
});
describe('visualBuffs', () => {
it('returns an array of buffs', () => {
vm.member = {
stats: {
class: 'Warrior',
},
};
expect(vm.visualBuffs).to.include({snowball: 'snowman'});
expect(vm.visualBuffs).to.include({spookySparkles: 'ghost'});
expect(vm.visualBuffs).to.include({shinySeed: 'avatar_floral_Warrior'});
expect(vm.visualBuffs).to.include({seafoam: 'seafoam_star'});
});
});
describe('backgroundClass', () => {
beforeEach(() => {
vm.member.preferences = { background: 'pony' };
});
it('shows the background', () => {
expect(vm.backgroundClass).to.equal('background_pony');
});
it('can be overridden', () => {
vm.overrideAvatarGear = { background: 'character' };
expect(vm.backgroundClass).to.equal('background_character');
});
it('returns to a blank string if not showing background', () => {
vm.withBackground = false;
vm.avatarOnly = true;
expect(vm.backgroundClass).to.equal('');
});
});
describe('specialMountClass', () => {
it('checks if riding a Kangaroo', () => {
vm.member = {
stats: {
class: 'None',
},
items: {},
};
expect(vm.specialMountClass).to.equal(undefined);
vm.member.items = {
currentMount: ['Kangaroo'],
};
expect(vm.specialMountClass).to.equal('offset-kangaroo');
});
});
describe('skinClass', () => {
it('returns current skin color', () => {
vm.member = {
stats: {},
preferences: {
skin: 'blue',
},
};
expect(vm.skinClass).to.equal('skin_blue');
});
it('returns if sleep or not', () => {
vm.member = {
stats: {},
preferences: {
skin: 'blue',
sleep: false,
},
};
expect(vm.skinClass).to.equal('skin_blue');
vm.member.preferences.sleep = true;
expect(vm.skinClass).to.equal('skin_blue_sleep');
});
});
context('methods', () => {
describe('getGearClass', () => {
beforeEach(() => {
vm.member = {
items: {
gear: {
equipped: { Hat: 'Fancy Tophat' },
},
},
preferences: { costume: false },
};
});
it('returns undefined if no match', () => {
expect(vm.getGearClass('foo')).to.equal(undefined);
});
it('returns the matching gear', () => {
expect(vm.getGearClass('Hat')).to.equal('Fancy Tophat');
});
it('can be overridden', () => {
vm.overrideAvatarGear = { Hat: 'Dapper Bowler' };
expect(vm.getGearClass('Hat')).to.equal('Dapper Bowler');
});
});
describe('hideGear', () => {
it('returns no weapon equipped', () => {
vm.member.items.gear.equipped = {};
expect(vm.hideGear('weapon')).to.equal(false);
});
beforeEach(() => {
vm.member = {
items: {
gear: {
equipped: {
weapon: {
baseWeapon: 'Spoon',
twoHanded: false,
},
},
},
},
preferences: { costume: false },
};
});
});
describe('show avatar', () => {
beforeEach(() => {
vm.member = {
stats: {
buffs: {
snowball: false,
seafoam: false,
spookySparkles: false,
shinySeed: false,
},
},
};
});
it('does if not showing visual buffs', () => {
expect(vm.showAvatar()).to.equal(true);
let buffs = vm.member.stats.buffs;
buffs.snowball = true;
expect(vm.showAvatar()).to.equal(false);
buffs.snowball = false;
buffs.spookySparkles = true;
expect(vm.showAvatar()).to.equal(false);
buffs.spookySparkles = false;
buffs.shinySeed = true;
expect(vm.showAvatar()).to.equal(false);
buffs.shinySeed = false;
buffs.seafoam = true;
expect(vm.showAvatar()).to.equal(false);
buffs.seafoam = false;
vm.showVisualBuffs = false;
expect(vm.showAvatar()).to.equal(true);
});
});
});
});

View File

@@ -1,4 +1,5 @@
import {shallow} from '@vue/test-utils';
import {mount} from '@vue/test-utils';
import Vue from 'vue';
import CategoryTags from 'client/components/categories/categoryTags.vue';
@@ -6,7 +7,7 @@ describe('Category Tags', () => {
let wrapper;
beforeEach(function () {
wrapper = shallow(CategoryTags, {
wrapper = mount(CategoryTags, {
propsData: {
categories: [],
},
@@ -27,8 +28,10 @@ describe('Category Tags', () => {
},
],
});
expect(wrapper.contains('.category-label')).to.eq(true);
expect(wrapper.find('.category-label').text()).to.eq('test');
return Vue.nextTick().then(() => {
expect(wrapper.contains('.category-label')).to.eq(true);
expect(wrapper.find('.category-label').text()).to.eq('test');
});
});
it('displays a habitica official in purple', () => {

View File

@@ -1,4 +1,4 @@
import { shallow } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import SidebarSection from 'client/components/sidebarSection.vue';
@@ -6,7 +6,7 @@ describe('Sidebar Section', () => {
let wrapper;
beforeEach(function () {
wrapper = shallow(SidebarSection, {
wrapper = mount(SidebarSection, {
propsData: {
title: 'Hello World',
},
@@ -39,7 +39,7 @@ describe('Sidebar Section', () => {
});
it('can hide contents by default', () => {
wrapper = shallow(SidebarSection, {
wrapper = mount(SidebarSection, {
propsData: {
title: 'Hello World',
show: false,

View File

@@ -1,4 +1,4 @@
import { shallow, createLocalVue } from '@vue/test-utils';
import { mount, createLocalVue } from '@vue/test-utils';
import TaskColumn from 'client/components/tasks/column.vue';
@@ -21,7 +21,7 @@ describe('Task Column', () => {
};
let stubs = ['b-modal']; // <b-modal> is a custom component and not tested here
return shallow(TaskColumn, {
return mount(TaskColumn, {
propsData: {
type,
},

View File

@@ -0,0 +1,39 @@
import {highlightUsers} from '../../../../../website/client/libs/highlightUsers';
import habiticaMarkdown from 'habitica-markdown';
describe('highlightUserAndEmail', () => {
it('highlights displayname', () => {
const text = 'hello @displayedUser with text after';
const result = highlightUsers(text, 'user', 'displayedUser');
expect(result).to.contain('<span class="at-text at-highlight">@displayedUser</span>');
});
it('highlights username', () => {
const text = 'hello @user';
const result = highlightUsers(text, 'user', 'displayedUser');
expect(result).to.contain('<span class="at-text at-highlight">@user</span>');
});
it('not highlights any email', () => {
const text = habiticaMarkdown.render('hello@example.com');
const result = highlightUsers(text, 'example', 'displayedUser');
expect(result).to.not.contain('<span class="at-highlight">@example</span>');
});
it('complex highlight', () => {
const plainText = 'a bit more @mentions to @use my@mentions.com broken.@mail.com';
const text = habiticaMarkdown.render(plainText);
const result = highlightUsers(text, 'use', 'mentions');
expect(result).to.contain('<span class="at-text at-highlight">@mentions</span>');
expect(result).to.contain('<span class="at-text at-highlight">@use</span>');
expect(result).to.not.contain('<span class="at-text at-highlight">@mentions</span>.com');
});
});

View File

@@ -0,0 +1,76 @@
import { data, gems, buffs, preferences, tasksOrder } from 'client/store/getters/user';
context('user getters', () => {
describe('data', () => {
it('returns the user\'s data', () => {
expect(data({
state: {
user: {
data: {
lvl: 1,
},
},
},
}).lvl).to.equal(1);
});
});
describe('gems', () => {
it('returns the user\'s gems', () => {
expect(gems({
state: {
user: {
data: { balance: 4.5 },
},
},
})).to.equal(18);
});
});
describe('buffs', () => {
it('returns the user\'s buffs', () => {
expect(buffs({
state: {
user: {
data: {
stats: {
buffs: [1],
},
},
},
},
})(0)).to.equal(1);
});
});
describe('preferences', () => {
it('returns the user\'s preferences', () => {
expect(preferences({
state: {
user: {
data: {
preferences: 1,
},
},
},
})).to.equal(1);
});
});
describe('tasksOrder', () => {
it('returns the user\'s tasksOrder', () => {
expect(tasksOrder({
state: {
user: {
tasksOrder: {
masters: 1,
},
},
},
})('master')).to.equal(1);
expect(tasksOrder()).to.not.equal('null');
expect(tasksOrder()).to.not.equal('undefined');
});
});
});

View File

@@ -1,13 +0,0 @@
import { gems as userGems } from 'client/store/getters/user';
describe('userGems getter', () => {
it('returns the user\'s gems', () => {
expect(userGems({
state: {
user: {
data: {balance: 4.5},
},
},
})).to.equal(18);
});
});

View File

@@ -62,6 +62,18 @@ describe('inAppRewards', () => {
expect(result[9].path).to.eql('potion');
});
it('ignores null/undefined entries', () => {
user.pinnedItems = testPinnedItems;
user.pinnedItems.push(null);
user.pinnedItems.push(undefined);
user.pinnedItemsOrder = testPinnedItemsOrder;
let result = inAppRewards(user);
expect(result[2].path).to.eql('armoire');
expect(result[9].path).to.eql('potion');
});
it('does not return seasonal items which have been unpinned', () => {
if (officialPinnedItems.length === 0) {
return; // if no seasonal items, this test is not applicable

View File

@@ -9,6 +9,7 @@ import {
import i18n from '../../../../website/common/script/i18n';
import content from '../../../../website/common/script/content/index';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
import { defaultsDeep } from 'lodash';
describe('shared.ops.buy', () => {
let user;
@@ -16,6 +17,10 @@ describe('shared.ops.buy', () => {
beforeEach(() => {
user = generateUser({
stats: { gp: 200 },
});
defaultsDeep(user, {
items: {
gear: {
owned: {
@@ -26,7 +31,6 @@ describe('shared.ops.buy', () => {
},
},
},
stats: { gp: 200 },
});
sinon.stub(analytics, 'track');
@@ -76,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,
});
});
@@ -138,4 +149,52 @@ describe('shared.ops.buy', () => {
buy(user, {params: {key: 'potion'}, quantity: 2});
expect(user.stats.hp).to.eql(50);
});
it('errors if user supplies a non-numeric quantity', (done) => {
try {
buy(user, {
params: {
key: 'dilatoryDistress1',
},
type: 'quest',
quantity: 'bogle',
});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(errorMessage('invalidQuantity'));
done();
}
});
it('errors if user supplies a negative quantity', (done) => {
try {
buy(user, {
params: {
key: 'dilatoryDistress1',
},
type: 'quest',
quantity: -3,
});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(errorMessage('invalidQuantity'));
done();
}
});
it('errors if user supplies a decimal quantity', (done) => {
try {
buy(user, {
params: {
key: 'dilatoryDistress1',
},
type: 'quest',
quantity: 1.83,
});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(errorMessage('invalidQuantity'));
done();
}
});
});

View File

@@ -158,7 +158,7 @@ describe('shared.ops.buyArmoire', () => {
expect(armoireCount).to.eql(_.size(getFullArmoire()) - 2);
expect(user.stats.gp).to.eql(100);
expect(analytics.track).to.be.calledOnce;
expect(analytics.track).to.be.calledTwice;
});
});
});

View File

@@ -11,6 +11,7 @@ import {
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
import { defaultsDeep } from 'lodash';
function buyGear (user, req, analytics) {
let buyOp = new BuyMarketGearOperation(user, req, analytics);
@@ -24,6 +25,10 @@ describe('shared.ops.buyMarketGear', () => {
beforeEach(() => {
user = generateUser({
stats: { gp: 200 },
});
defaultsDeep(user, {
items: {
gear: {
owned: {
@@ -34,7 +39,6 @@ describe('shared.ops.buyMarketGear', () => {
},
},
},
stats: { gp: 200 },
});
sinon.stub(shared, 'randomVal');
@@ -71,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

@@ -108,6 +108,47 @@ describe('shared.ops.purchase', () => {
done();
}
});
it('returns error when user supplies a non-numeric quantity', (done) => {
let type = 'eggs';
let key = 'Wolf';
try {
purchase(user, {params: {type, key}, quantity: 'jamboree'}, analytics);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
done();
}
});
it('returns error when user supplies a negative quantity', (done) => {
let type = 'eggs';
let key = 'Wolf';
user.balance = 10;
try {
purchase(user, {params: {type, key}, quantity: -2}, analytics);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
done();
}
});
it('returns error when user supplies a decimal quantity', (done) => {
let type = 'eggs';
let key = 'Wolf';
user.balance = 10;
try {
purchase(user, {params: {type, key}, quantity: 2.9}, analytics);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
done();
}
});
});
context('successful purchase', () => {
@@ -168,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

@@ -169,6 +169,24 @@ describe('shared.ops.feed', () => {
expect(user.items.pets['Wolf-Base']).to.equal(7);
});
it('awards All Your Base achievement', () => {
user.items.pets['Wolf-Spooky'] = 5;
user.items.food.Milk = 2;
user.items.mounts = {
'Wolf-Base': true,
'TigerCub-Base': true,
'PandaCub-Base': true,
'LionCub-Base': true,
'Fox-Base': true,
'FlyingPig-Base': true,
'Dragon-Base': true,
'Cactus-Base': true,
'BearCub-Base': true,
};
feed(user, {params: {pet: 'Wolf-Spooky', food: 'Milk'}});
expect(user.achievements.allYourBase).to.eql(true);
});
it('evolves the pet into a mount when feeding user.items.pets[pet] >= 50', () => {
user.items.pets['Wolf-Base'] = 49;
user.items.food.Milk = 2;

View File

@@ -93,6 +93,22 @@ describe('shared.ops.hatch', () => {
done();
}
});
it('does not allow hatching quest pet egg using wacky potion', (done) => {
user.items.eggs = {Bunny: 1};
user.items.hatchingPotions = {Veggie: 1};
user.items.pets = {};
try {
hatch(user, {params: {egg: 'Bunny', hatchingPotion: 'Veggie'}});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('messageInvalidEggPotionCombo'));
expect(user.items.pets).to.be.empty;
expect(user.items.eggs).to.eql({Bunny: 1});
expect(user.items.hatchingPotions).to.eql({Veggie: 1});
done();
}
});
});
context('successful hatching', () => {
@@ -143,6 +159,24 @@ describe('shared.ops.hatch', () => {
expect(user.items.eggs).to.eql({Wolf: 0});
expect(user.items.hatchingPotions).to.eql({Base: 0});
});
it('awards Back to Basics achievement', () => {
user.items.pets = {
'Wolf-Base': 5,
'TigerCub-Base': 5,
'PandaCub-Base': 10,
'LionCub-Base': 5,
'Fox-Base': 5,
'FlyingPig-Base': 5,
'Dragon-Base': 5,
'Cactus-Base': 15,
'BearCub-Base': 5,
};
user.items.eggs = {Wolf: 1};
user.items.hatchingPotions = {Spooky: 1};
hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Spooky'}});
expect(user.achievements.backToBasics).to.eql(true);
});
});
});
});

View File

@@ -0,0 +1,18 @@
import {
generateUser,
} from '../../helpers/common.helper';
import {addPinnedGear} from '../../../website/common/script/ops/pinnedGearUtils';
describe('shared.ops.pinnedGearUtils.addPinnedGear', () => {
let user;
beforeEach(() => {
user = generateUser();
});
it('not adds an item with empty properties to pinnedItems', () => {
addPinnedGear(user, undefined, undefined);
expect(user.pinnedItems.length).to.be.eql(0);
});
});

View File

@@ -49,6 +49,7 @@ describe('shared.ops.rebirth', () => {
let [, message] = rebirth(user);
expect(message).to.equal(i18n.t('rebirthComplete'));
expect(user.flags.lastFreeRebirth).to.exist;
});
it('rebirths a user with not enough gems but more than max level', () => {
@@ -60,6 +61,16 @@ describe('shared.ops.rebirth', () => {
expect(message).to.equal(i18n.t('rebirthComplete'));
});
it('rebirths a user using gems if over max level but rebirthed recently', () => {
user.stats.lvl = MAX_LEVEL + 1;
user.flags.lastFreeRebirth = new Date();
let [, message] = rebirth(user);
expect(message).to.equal(i18n.t('rebirthComplete'));
expect(user.balance).to.equal(0);
});
it('resets user\'s tasks values except for rewards to 0', () => {
tasks[0].value = 1;
tasks[1].value = 1;

View File

@@ -9,13 +9,14 @@ import hatchingPotions from '../../website/common/script/content/hatching-potion
describe('hatchingPotions', () => {
describe('all', () => {
it('is a combination of drop and premium potions', () => {
it('is a combination of drop, premium, and wacky potions', () => {
let dropNumber = Object.keys(hatchingPotions.drops).length;
let premiumNumber = Object.keys(hatchingPotions.premium).length;
let wackyNumber = Object.keys(hatchingPotions.wacky).length;
let allNumber = Object.keys(hatchingPotions.all).length;
expect(allNumber).to.be.greaterThan(0);
expect(allNumber).to.equal(dropNumber + premiumNumber);
expect(allNumber).to.equal(dropNumber + premiumNumber + wackyNumber);
});
it('contains basic information about each potion', () => {

View File

@@ -47,6 +47,18 @@ describe('stable', () => {
});
});
describe('wackyPets', () => {
it('contains a pet for each wacky potion * each drop egg', () => {
let numberOfWackyPotions = Object.keys(potions.wacky).length;
let numberOfDropEggs = Object.keys(eggs.drops).length;
let numberOfWackyPets = Object.keys(stable.wackyPets).length;
let expectedTotal = numberOfWackyPotions * numberOfDropEggs;
expect(numberOfWackyPets).to.be.greaterThan(0);
expect(numberOfWackyPets).to.equal(expectedTotal);
});
});
describe('specialPets', () => {
it('each value is a valid translation string', () => {
each(stable.specialPets, (pet) => {
@@ -107,10 +119,11 @@ describe('stable', () => {
let questNumber = Object.keys(stable.questPets).length;
let specialNumber = Object.keys(stable.specialPets).length;
let premiumNumber = Object.keys(stable.premiumPets).length;
let wackyNumber = Object.keys(stable.wackyPets).length;
let allNumber = Object.keys(stable.petInfo).length;
expect(allNumber).to.be.greaterThan(0);
expect(allNumber).to.equal(dropNumber + questNumber + specialNumber + premiumNumber);
expect(allNumber).to.equal(dropNumber + questNumber + specialNumber + premiumNumber + wackyNumber);
});
it('contains basic information about each pet', () => {

View File

@@ -4,6 +4,7 @@ import { requester } from './requester';
import {
getDocument as getDocumentFromMongo,
updateDocument as updateDocumentInMongo,
unsetDocument as unsetDocumentInMongo,
} from '../mongo';
import {
assign,
@@ -29,6 +30,18 @@ class ApiObject {
return this;
}
async unset (options) {
if (isEmpty(options)) {
return;
}
await unsetDocumentInMongo(this._docType, this, options);
_updateLocalParameters((this, options));
return this;
}
async sync () {
let updatedDoc = await getDocumentFromMongo(this._docType, this);

View File

@@ -13,10 +13,16 @@ import * as Tasks from '../../../../website/server/models/task';
// parameter, such as the number of wolf eggs the user has,
// , you can do so by passing in the full path as a string:
// { 'items.eggs.Wolf': 10 }
export async function generateUser (update = {}) {
let username = (Date.now() + generateUUID()).substring(0, 20);
let password = 'password';
let email = `${username}@example.com`;
//
// To manually set a username, email or password pass it in as
// an object for the second parameter. Only overrides need to be
// added. Items that don't exist will be autogenerated.
// Example: generateUser({}, { username: 'TestName' }) adds user
// with the 'TestName' username.
export async function generateUser (update = {}, overrides = {}) {
let username = overrides.username || (Date.now() + generateUUID()).substring(0, 20);
let password = overrides.password || 'password';
let email = overrides.email || `${username}@example.com`;
let user = await requester().post('/user/auth/local/register', {
username,

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

@@ -9,6 +9,7 @@ import {
} from '../../website/server/models/task';
export {translate} from './translate';
export function generateUser (options = {}) {
let user = new User(options).toObject();

View File

@@ -98,6 +98,19 @@ export async function updateDocument (collectionName, doc, update) {
});
}
// Unset a property in the database.
// Useful for testing.
export async function unsetDocument (collectionName, doc, update) {
let collection = mongoose.connection.db.collection(collectionName);
return new Promise((resolve) => {
collection.updateOne({ _id: doc._id }, { $unset: update }, (updateErr) => {
if (updateErr) throw new Error(`Error updating ${collectionName}: ${updateErr}`);
resolve();
});
});
}
export async function getDocument (collectionName, doc) {
let collection = mongoose.connection.db.collection(collectionName);

View File

@@ -0,0 +1,20 @@
import mongoose from 'mongoose';
export async function mockFindById (response) {
const mockFind = {
select () {
return this;
},
lean () {
return this;
},
exec () {
return Promise.resolve(response);
},
};
sinon.stub(mongoose.Model, 'findById').returns(mockFind);
}
export function restoreFindById () {
return mongoose.Model.findById.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

@@ -65,7 +65,7 @@ apt-get install -qq ntp
echo Installing nvm, node and global node modules...
/vagrant/vagrant_scripts/install_node.sh
echo "'vagrant up' is finished. Continue with the instructions at http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally"
echo "'vagrant up' is finished. Continue with the instructions at http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally"
# Uncomment both lines to autostart the habitica server when provisioning
# echo Starting Habitica server...

View File

@@ -1,5 +1,5 @@
# Running
For information about installing and running Habitica locally, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
For information about installing and running Habitica locally, see [Setting up Habitica Locally](http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally).
# Preparation Reading
- Vue 2 (https://vuejs.org)
@@ -18,4 +18,4 @@ The project is developed directly in the `develop` branch as long as we'll be ab
So far most of the work has been on the template, so there's no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: it's basically a Flux implementation: there's a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly.
For further resources, see [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths), and in particular the ["Website Technology Stack" section](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths#Website_Technology_Stack).
For further resources, see [Guidance for Blacksmiths](http://habitica.fandom.com/wiki/Guidance_for_Blacksmiths), and in particular the ["Website Technology Stack" section](http://habitica.fandom.com/wiki/Guidance_for_Blacksmiths#Website_Technology_Stack).

View File

@@ -12,22 +12,24 @@ 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)
template(v-if="isUserLoaded")
div.resting-banner(v-show="showRestingBanner", ref="restingBanner")
.resting-banner(v-show="showRestingBanner", ref="restingBanner")
span.content
span.label.d-inline.d-sm-none {{ $t('innCheckOutBannerShort') }}
span.label.d-none.d-sm-inline {{ $t('innCheckOutBanner') }}
span.separator |
span.resume(@click="resumeDamage()") {{ $t('resumeDamage') }}
div.closepadding(@click="hideBanner()")
.closepadding(@click="hideBanner()")
span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close")
notifications-display
app-menu(:class='{"restingInn": showRestingBanner}' :style="{ marginTop: bannerHeight + 'px' }")
app-menu
.container-fluid
app-header(:class='{"restingInn": showRestingBanner}')
app-header
buyModal(
:item="selectedItemToBuy || {}",
:withPin="true",
@@ -50,6 +52,13 @@ div
<style lang='scss' scoped>
@import '~client/assets/scss/colors.scss';
#app {
height: calc(100% - 56px); /* 56px is the menu */
display: flex;
flex-direction: column;
min-height: 100vh;
}
#loading-screen-inapp {
#melior {
margin: 0 auto;
@@ -79,6 +88,11 @@ div
cursor: crosshair;
}
.container-fluid {
overflow-x: hidden;
flex: 1 0 auto;
}
.notification {
border-radius: 1000px;
background-color: $green-10;
@@ -89,42 +103,10 @@ div
margin-bottom: .5em;
}
.container-fluid {
overflow-x: hidden;
flex: 1 0 auto;
}
#app {
height: calc(100% - 56px); /* 56px is the menu */
display: flex;
flex-direction: column;
min-height: 100vh;
}
</style>
<style lang='scss'>
@import '~client/assets/scss/colors.scss';
/* @TODO: The modal-open class is not being removed. Let's try this for now */
.modal {
overflow-y: scroll !important;
}
.modal-backdrop.show {
opacity: .9 !important;
background-color: $purple-100 !important;
}
/* Push progress bar above modals */
#nprogress .bar {
z-index: 1600 !important; /* Must stay above nav bar */
}
.resting-banner {
width: 100%;
min-height: 40px;
background-color: $blue-10;
position: fixed;
top: 0;
z-index: 1300;
display: flex;
@@ -140,14 +122,10 @@ div
.closepadding {
margin: 11px 24px;
display: inline-block;
position: absolute;
position: relative;
right: 0;
top: 0;
cursor: pointer;
span svg path {
stroke: $blue-500;
}
}
@media only screen and (max-width: 768px) {
@@ -170,6 +148,30 @@ div
}
</style>
<style lang='scss'>
@import '~client/assets/scss/colors.scss';
.closepadding span svg path {
stroke: #FFF;
opacity: 0.48;
}
/* @TODO: The modal-open class is not being removed. Let's try this for now */
.modal {
overflow-y: scroll !important;
}
.modal-backdrop.show {
opacity: .9 !important;
background-color: $purple-100 !important;
}
/* Push progress bar above modals */
#nprogress .bar {
z-index: 1600 !important; /* Must stay above nav bar */
}
</style>
<script>
import axios from 'axios';
import { loadProgressBar } from 'axios-progress-bar';
@@ -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 {
@@ -225,7 +231,6 @@ export default {
loading: true,
currentTipNumber: 0,
bannerHidden: false,
bannerHeight: 0,
};
},
computed: {
@@ -318,6 +323,7 @@ export default {
const errorMessage = errorData.message || errorData;
// Check for conditions to reset the user auth
// TODO use a specific error like NotificationNotFound instead of checking for the string
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
if (invalidUserMessage.indexOf(errorMessage) !== -1) {
this.$store.dispatch('auth:logout');
@@ -327,12 +333,6 @@ export default {
let snackbarTimeout = false;
if (error.response.status === 502) snackbarTimeout = true;
const notificationNotFoundMessage = [
this.$t('messageNotificationNotFound'),
this.$t('messageNotificationNotFound', 'en'),
];
if (notificationNotFoundMessage.indexOf(errorMessage) !== -1) snackbarTimeout = true;
let errorsToShow = [];
// show only the first error for each param
let paramErrorsFound = {};
@@ -346,13 +346,17 @@ export default {
} else {
errorsToShow.push(errorMessage);
}
// dispatch as one snackbar notification
this.$store.dispatch('snackbars:add', {
title: 'Habitica',
text: errorsToShow.join(' '),
type: 'error',
timeout: snackbarTimeout,
});
// Ignore NotificationNotFound errors, see https://github.com/HabitRPG/habitica/issues/10391
if (errorData.error !== 'NotificationNotFound') {
// dispatch as one snackbar notification
this.$store.dispatch('snackbars:add', {
title: 'Habitica',
text: errorsToShow.join(' '),
type: 'error',
timeout: snackbarTimeout,
});
}
}
return Promise.reject(error);
@@ -423,14 +427,6 @@ export default {
this.hideLoadingScreen();
window.addEventListener('resize', this.setBannerOffset);
// Adjust the positioning of the header banners
this.$watch('showRestingBanner', () => {
this.$nextTick(() => {
this.setBannerOffset();
});
}, {immediate: true});
// Adjust the timezone offset
if (this.user.preferences.timezoneOffset !== this.browserTimezoneOffset) {
this.$store.dispatch('user:set', {
@@ -443,7 +439,7 @@ export default {
appState = JSON.parse(appState);
if (appState.paymentCompleted) {
removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
this.$root.$emit('bv::show::modal', 'payments-success-modal');
this.$root.$emit('habitica:payment-success', appState);
}
}
this.$nextTick(() => {
@@ -465,7 +461,6 @@ export default {
this.$root.$off('bv::show::modal');
this.$root.$off('buyModal::showItem');
this.$root.$off('selectMembersModal::showItem');
window.removeEventListener('resize', this.setBannerOffset);
},
mounted () {
// Remove the index.html loading screen and now show the inapp loading
@@ -624,22 +619,10 @@ export default {
},
hideBanner () {
this.bannerHidden = true;
this.setBannerOffset();
},
resumeDamage () {
this.$store.dispatch('user:sleep');
},
setBannerOffset () {
let contentPlacement = 0;
if (this.showRestingBanner && this.$refs.restingBanner !== undefined) {
contentPlacement = this.$refs.restingBanner.clientHeight;
}
this.bannerHeight = contentPlacement;
let smartBanner = document.getElementsByClassName('smartbanner')[0];
if (smartBanner !== undefined) {
smartBanner.style.top = `${contentPlacement}px`;
}
},
},
};
</script>
@@ -672,5 +655,7 @@ export default {
<style src="assets/css/sprites/spritesmith-main-21.css"></style>
<style src="assets/css/sprites/spritesmith-main-22.css"></style>
<style src="assets/css/sprites/spritesmith-main-23.css"></style>
<style src="assets/css/sprites/spritesmith-main-24.css"></style>
<style src="assets/css/sprites/spritesmith-main-25.css"></style>
<style src="assets/css/sprites.css"></style>
<style src="smartbanner.js/dist/smartbanner.min.css"></style>

View File

@@ -10,6 +10,12 @@
height: 219px;
}
.Pet_HatchingPotion_Veggie {
background: url("~assets/images/Pet_HatchingPotion_Veggie.gif") no-repeat;
width: 68px;
height: 68px;
}
.Gems {
display:inline-block;
margin-right:5px;

View File

@@ -1,96 +1,72 @@
.achievement-costumeContest6x {
.promo_armoire_backgrounds_201906 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1136px -438px;
width: 144px;
height: 156px;
}
.promo_alligator {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 480px;
height: 360px;
}
.promo_animal_tails {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -994px 0px;
width: 141px;
height: 441px;
}
.promo_armoire_backgrounds_201812 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -723px;
background-position: 0px -720px;
width: 423px;
height: 147px;
}
.promo_bird_buddies_bundle {
.promo_bronze_quest {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -723px;
width: 420px;
background-position: 0px -359px;
width: 360px;
height: 360px;
}
.promo_dolphin_quest {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 553px;
height: 358px;
}
.promo_floral_sunshine_potions {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -720px;
width: 423px;
height: 147px;
}
.promo_frost_potions {
.promo_halfmoon_glasses {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -421px -871px;
width: 417px;
height: 147px;
}
.promo_ios {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -361px;
width: 375px;
height: 361px;
}
.promo_mystery_201811 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1136px -142px;
width: 282px;
height: 147px;
}
.promo_piyo {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1136px -290px;
background-position: -963px -296px;
width: 279px;
height: 147px;
}
.promo_mystery_201905 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -963px -148px;
width: 282px;
height: 147px;
}
.promo_oddballs_bundle {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -963px 0px;
width: 423px;
height: 147px;
}
.promo_orcas {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -963px -444px;
width: 219px;
height: 147px;
}
.promo_seasonal_shop {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1183px -444px;
width: 162px;
height: 132px;
}
.promo_summer_splash_2019 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -554px 0px;
width: 408px;
height: 186px;
}
.promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1281px -438px;
background-position: -1246px -148px;
width: 96px;
height: 69px;
}
.promo_turkey_day_2018 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -871px;
width: 420px;
height: 147px;
}
.promo_veteran_pets {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1136px 0px;
width: 363px;
height: 141px;
}
.scene_hiking {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -481px -420px;
background-position: -554px -187px;
width: 258px;
height: 258px;
}
.scene_nametag {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -481px 0px;
width: 512px;
height: 208px;
}
.scene_sleep {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -481px -209px;
width: 390px;
height: 210px;
}
.scene_veteran_pets {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1136px -595px;
width: 242px;
height: 62px;
}

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

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