Compare commits

..

285 Commits

Author SHA1 Message Date
Sabe Jones 043696c225 4.102.0 2019-06-20 15:13:55 -05:00
Sabe Jones 69c2b0378f chore(sprites): compile 2019-06-20 15:13:27 -05:00
Sabe Jones fcfc44fd13 feat(content): Watery Potions
also fixes some sprite issues
2019-06-20 15:13:19 -05:00
Sabe Jones 76773d27c6 4.101.1 2019-06-18 23:29:07 -05:00
Sabe Jones b3e81177ec fix(content): correct Mage weapon price, smarten Rebirth banner 2019-06-18 23:29:00 -05:00
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
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
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
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
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
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
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
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
Ian Oxley 08c6e8298c Fix linting errors
Remove parentheses and add trailing comma.
2018-11-24 17:12:12 +00:00
Ian Oxley 5a30b0cf1f Add aria-label to Habitica logo 2018-11-23 22:44:31 +00:00
Ian Oxley cf847cd1d8 Add aria-label to notifaction and user menus
Add aria-label to give a text equivalent for non-visual users.
2018-11-23 22:44:31 +00:00
Ian Oxley 5753d3e648 Improve a11y for the dropdown menu
Add role="button" to make the component report itself as a button.

Add tabindex so the menu toggle can receive keyboard focus.

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

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

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

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

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

Make the gems icon a link, with a href set to `#buy-gems`, which is the element
that contains the gems dialog.
2018-11-23 22:30:43 +00:00
Mateus Etto ef07abfd28 Merge branch 'develop' into party-chat-translations
# Conflicts:
#	test/api/unit/models/group.test.js
#	website/server/controllers/api-v3/user/spells.js
#	website/server/models/chat.js
#	website/server/models/group.js
2018-10-08 18:44:02 +09:00
Mateus Etto b28adc6b42 Merge branch 'develop' into party-chat-translations 2018-08-29 20:07:19 +09:00
Mateus Etto c814eabb29 Merge branch 'develop' into party-chat-translations 2018-07-21 16:06:38 +09:00
Mateus Etto 4dc19a8c60 Merge branch 'develop' into party-chat-translations 2018-07-12 06:28:44 +09:00
Mateus Etto 89f6a9b07e Merge branch 'develop' into party-chat-translations
# Conflicts:
#	test/api/unit/models/group.test.js
2018-06-20 20:24:38 +09:00
Mateus Etto a23926f34f Merge branch 'develop' into party-chat-translations 2018-05-19 10:21:54 +09:00
Mateus Etto 118198b594 Fixed tests that contains random data 2018-05-19 00:26:28 +09:00
Mateus Etto 97e80e2093 Implemented requested changes 2018-05-18 23:08:37 +09:00
Mateus Etto 2588befceb Merge branch 'develop' into party-chat-translations
# Conflicts:
#	website/server/models/group.js
2018-05-06 00:46:47 +09:00
Mateus Etto f09274225a Update chat schema: add 'info' field 2018-04-25 21:28:02 +09:00
Mateus Etto 03b66abe70 Merge branch 'develop' into party-chat-translations
# Conflicts:
#	website/server/controllers/api-v3/quests.js
#	website/server/controllers/api-v3/tasks/groups.js
#	website/server/controllers/api-v3/user/spells.js
#	website/server/models/group.js
2018-04-25 21:15:49 +09:00
Mateus Etto 9d41fb0252 Add tests + some small adjusts 2018-03-25 00:01:20 +09:00
Mateus Etto 012fa2f8ef Improve readability of translateSystemMessages function 2018-03-18 12:09:03 +09:00
Mateus Etto 00d8d9d0cc Merge branch 'develop' into party-chat-translations 2018-03-17 00:08:20 +09:00
Mateus Etto cc4772c75a Update test 2018-03-12 23:48:09 +09:00
Mateus Etto fabaaa6d92 Merge branch 'develop' into party-chat-translations 2018-03-12 22:35:13 +09:00
Mateus Etto 854696728a Fix message when boss don't attack 2018-03-12 21:50:23 +09:00
Mateus Etto 314926cc06 Hard coded messages now using i18n strings 2018-03-12 21:45:41 +09:00
Mateus Etto 68fa834946 spellName -> spell; grammar fix 2018-03-12 21:08:20 +09:00
Mateus Etto 09440d5adf Merge branch 'develop' into party-chat-translations 2018-03-02 19:28:41 +09:00
Mateus Etto 1724bfc553 Merge branch 'develop' into party-chat-translations
# Conflicts:
#	website/server/models/group.js
2018-02-28 19:42:19 +09:00
Mateus Etto 8304f99ecb Added comment explaining the new info object 2018-02-26 20:00:47 +09:00
Mateus Etto d2fcdf4493 Merge branch 'develop' into party-chat-translations
# Conflicts:
#	website/server/controllers/api-v3/user.js
2018-02-26 19:55:32 +09:00
Mateus Etto 649404ac6a Fix travis-ci error 2018-02-19 20:57:31 +09:00
Mateus Etto 774db2564f Moving some strings around 2018-02-19 20:17:03 +09:00
Mateus Etto 521a1e646d Missing message (user claim task) 2018-02-19 20:07:05 +09:00
Mateus Etto 2619ac37d9 Fix mistake on tavern_boss_rage_tired 2018-02-19 00:36:49 +09:00
Mateus Etto 2ce9b319a0 Target username instead of uuid 2018-02-19 00:24:30 +09:00
Mateus Etto 477c23dd67 Save username instead of uuid (no queries necessary anymore) 2018-02-19 00:18:29 +09:00
Mateus Etto 7af71d1457 Fix errors 2018-02-18 19:44:17 +09:00
Mateus Etto 3171550de2 Fix syntax problems found when running tests 2018-02-18 17:14:18 +09:00
Mateus Etto 6477801d3e Translation support for missing sendChat calls 2018-02-18 15:26:27 +09:00
Mateus Etto 14798ced82 Translation support for a lot of messages in party 2018-02-17 22:17:08 +09:00
Mateus Etto 34d37cefcc Boss damage messages translation support 2018-02-17 19:41:15 +09:00
Mateus Etto bd21933cea Quest started translation support 2018-02-17 17:15:14 +09:00
879 changed files with 41134 additions and 36404 deletions
+5 -18
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
+2 -2
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",
+37 -6
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
+1 -1
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';
}
@@ -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
}
};
@@ -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
}
};
+1 -1
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';
+43 -76
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
}
};
+2 -2
View File
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
const MIGRATION_NAME = 'mystery_items_201903';
const MYSTERY_ITEMS = ['armor_mystery_201903', 'head_mystery_201903'];
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';
+1095 -1599
View File
File diff suppressed because it is too large Load Diff
+34 -34
View File
@@ -1,20 +1,20 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.90.3",
"version": "4.102.0",
"main": "./website/server/index.js",
"dependencies": {
"@google-cloud/trace-agent": "^3.5.2",
"@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": "^4.6.0-beta.2",
"amplitude-js": "^5.0.0",
"apidoc": "^0.17.5",
"apn": "^2.2.0",
"autoprefixer": "^8.5.0",
"aws-sdk": "^2.400.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",
@@ -28,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.13",
"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",
@@ -48,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.4.11",
"mongoose": "^5.4.19",
"morgan": "^1.7.0",
"nconf": "^0.10.0",
"node-gcm": "^1.0.2",
"node-sass": "^4.9.0",
"nodemailer": "^5.0.0",
"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",
@@ -85,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",
@@ -98,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.6.4",
"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.6.4",
"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",
@@ -152,9 +152,9 @@
"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",
@@ -166,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",
@@ -181,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",
+1 -1
View File
@@ -53,7 +53,7 @@ async function _deleteHabiticaData (user, email) {
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}`);
}
}
+3 -4
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 = {
+113
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);
});
});
});
+26 -3
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();
});
+368 -35
View File
@@ -1,7 +1,7 @@
import moment from 'moment';
import { v4 as generateUUID } from 'uuid';
import validator from 'validator';
import { sleep } from '../../../helpers/api-unit.helper';
import { sleep, translationCheck } from '../../../helpers/api-unit.helper';
import {
SPAM_MESSAGE_LIMIT,
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
@@ -271,7 +271,16 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member attacks Wailing Whale for 5.0 damage.` `Wailing Whale attacks party for 7.5 damage.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`Participating Member attacks Wailing Whale for 5.0 damage. Wailing Whale attacks party for 7.5 damage.`',
info: {
bossDamage: '7.5',
quest: 'whale',
type: 'boss_damage',
user: 'Participating Member',
userDamage: '5.0',
},
});
});
it('applies damage only to participating members of party', async () => {
@@ -344,7 +353,10 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledTwice;
expect(Group.prototype.sendChat).to.be.calledWith('`You defeated Wailing Whale! Questing party members receive the rewards of victory.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`You defeated Wailing Whale! Questing party members receive the rewards of victory.`',
info: { quest: 'whale', type: 'boss_defeated' },
});
});
it('calls finishQuest when boss has <= 0 hp', async () => {
@@ -387,7 +399,10 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledWith(quest.boss.rage.effect('en'));
expect(Group.prototype.sendChat).to.be.calledWith({
message: quest.boss.rage.effect('en'),
info: { quest: 'trex_undead', type: 'boss_rage' },
});
expect(party.quest.progress.hp).to.eql(383.5);
expect(party.quest.progress.rage).to.eql(0);
});
@@ -437,7 +452,10 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledWith(quest.boss.rage.effect('en'));
expect(Group.prototype.sendChat).to.be.calledWith({
message: quest.boss.rage.effect('en'),
info: { quest: 'lostMasterclasser4', type: 'boss_rage' },
});
expect(party.quest.progress.rage).to.eql(0);
let drainedUser = await User.findById(participatingMember._id);
@@ -488,7 +506,15 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member found 5 Bars of Soap.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`Participating Member found 5 Bars of Soap.`',
info: {
items: { soapBars: 5 },
quest: 'atom1',
type: 'user_found_items',
user: 'Participating Member',
},
});
});
it('sends a chat message if no progress is made', async () => {
@@ -499,7 +525,15 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member found 0 Bars of Soap.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`Participating Member found 0 Bars of Soap.`',
info: {
items: { soapBars: 0 },
quest: 'atom1',
type: 'user_found_items',
user: 'Participating Member',
},
});
});
it('sends a chat message if no progress is made on quest with multiple items', async () => {
@@ -516,9 +550,15 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWithMatch(/`Participating Member found/);
expect(Group.prototype.sendChat).to.be.calledWithMatch(/0 Blue Fins/);
expect(Group.prototype.sendChat).to.be.calledWithMatch(/0 Fire Coral/);
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`Participating Member found 0 Fire Coral, 0 Blue Fins.`',
info: {
items: { blueFins: 0, fireCoral: 0 },
quest: 'dilatoryDistress1',
type: 'user_found_items',
user: 'Participating Member',
},
});
});
it('handles collection quests with multiple items', async () => {
@@ -535,8 +575,14 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWithMatch(/`Participating Member found/);
expect(Group.prototype.sendChat).to.be.calledWithMatch(/\d* (Tracks|Broken Twigs)/);
expect(Group.prototype.sendChat).to.be.calledWithMatch({
message: sinon.match(/`Participating Member found/).and(sinon.match(/\d* (Tracks|Broken Twigs)/)),
info: {
quest: 'evilsanta2',
type: 'user_found_items',
user: 'Participating Member',
},
});
});
it('sends message about victory', async () => {
@@ -547,7 +593,10 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledTwice;
expect(Group.prototype.sendChat).to.be.calledWith('`All items found! Party has received their rewards.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`All items found! Party has received their rewards.`',
info: { type: 'all_items_found' },
});
});
it('calls finishQuest when all items are found', async () => {
@@ -718,6 +767,258 @@ describe('Group Model', () => {
expect(res.t).to.not.be.called;
});
});
describe('translateSystemMessages', () => {
it('translate quest_start', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'quest_start',
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate boss_damage', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'boss_damage',
user: questLeader.profile.name,
quest: 'basilist',
userDamage: 15.3,
bossDamage: 3.7,
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate boss_dont_attack', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'boss_dont_attack',
user: questLeader.profile.name,
quest: 'basilist',
userDamage: 15.3,
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate boss_rage', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'boss_rage',
quest: 'lostMasterclasser3',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate boss_defeated', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'boss_defeated',
quest: 'lostMasterclasser3',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate user_found_items', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'user_found_items',
user: questLeader.profile.name,
quest: 'lostMasterclasser1',
items: {
ancientTome: 3,
forbiddenTome: 2,
hiddenTome: 1,
},
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate all_items_found', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'all_items_found',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate spell_cast_party', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'spell_cast_party',
user: questLeader.profile.name,
class: 'wizard',
spell: 'earth',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate spell_cast_user', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'spell_cast_user',
user: questLeader.profile.name,
class: 'special',
spell: 'snowball',
target: participatingMember.profile.name,
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate quest_cancel', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'quest_cancel',
user: questLeader.profile.name,
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate quest_abort', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'quest_abort',
user: questLeader.profile.name,
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate tavern_quest_completed', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'tavern_quest_completed',
quest: 'stressbeast',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate tavern_boss_rage_tired', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'tavern_boss_rage_tired',
quest: 'stressbeast',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate tavern_boss_rage', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'tavern_boss_rage',
quest: 'dysheartener',
scene: 'market',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate tavern_boss_desperation', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'tavern_boss_desperation',
quest: 'stressbeast',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate claim_task', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'claim_task',
user: questLeader.profile.name,
task: 'Feed the pet',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
});
describe('toJSONCleanChat', () => {
it('shows messages with 1 flag to non-admins', async () => {
party.chat = [{
flagCount: 1,
info: {
type: 'quest_start',
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
expect(toJSON.chat.length).to.equal(1);
});
it('shows messages with >= 2 flag to admins', async () => {
party.chat = [{
flagCount: 3,
info: {
type: 'quest_start',
quest: 'basilist',
},
}];
const admin = new User({'contributor.admin': true});
let toJSON = await Group.toJSONCleanChat(party, admin);
expect(toJSON.chat.length).to.equal(1);
});
it('doesn\'t show flagged messages to non-admins', async () => {
party.chat = [{
flagCount: 3,
info: {
type: 'quest_start',
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
expect(toJSON.chat.length).to.equal(0);
});
});
});
context('Instance Methods', () => {
@@ -1007,20 +1308,22 @@ describe('Group Model', () => {
});
it('formats message', () => {
const chatMessage = party.sendChat('a new message', {
_id: 'user-id',
profile: { name: 'user name' },
contributor: {
toObject () {
return 'contributor object';
const chatMessage = party.sendChat({
message: 'a new message', user: {
_id: 'user-id',
profile: { name: 'user name' },
contributor: {
toObject () {
return 'contributor object';
},
},
},
backer: {
toObject () {
return 'backer object';
backer: {
toObject () {
return 'backer object';
},
},
},
});
}}
);
const chat = chatMessage;
@@ -1037,7 +1340,7 @@ describe('Group Model', () => {
});
it('formats message as system if no user is passed in', () => {
const chat = party.sendChat('a system message');
const chat = party.sendChat({message: 'a system message'});
expect(chat.text).to.eql('a system message');
expect(validator.isUUID(chat.id)).to.eql(true);
@@ -1052,7 +1355,7 @@ describe('Group Model', () => {
});
it('updates users about new messages in party', () => {
party.sendChat('message');
party.sendChat({message: 'message'});
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({
@@ -1066,7 +1369,7 @@ describe('Group Model', () => {
type: 'guild',
});
group.sendChat('message');
group.sendChat({message: 'message'});
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({
@@ -1076,7 +1379,7 @@ describe('Group Model', () => {
});
it('does not send update to user that sent the message', () => {
party.sendChat('message', {_id: 'user-id', profile: { name: 'user' }});
party.sendChat({message: 'message', user: {_id: 'user-id', profile: { name: 'user' }}});
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({
@@ -1088,7 +1391,7 @@ describe('Group Model', () => {
it('skips sending new message notification for guilds with > 5000 members', () => {
party.memberCount = 5001;
party.sendChat('message');
party.sendChat({message: 'message'});
expect(User.update).to.not.be.called;
});
@@ -1096,7 +1399,7 @@ describe('Group Model', () => {
it('skips sending messages to the tavern', () => {
party._id = TAVERN_ID;
party.sendChat('message');
party.sendChat({message: 'message'});
expect(User.update).to.not.be.called;
});
@@ -1431,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;
@@ -1579,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);
@@ -1715,13 +2048,13 @@ describe('Group Model', () => {
questLeader = await User.findById(questLeader._id);
participatingMember = await User.findById(participatingMember._id);
expect(questLeader.party.quest.completed).to.eql('whale');
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('whale');
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);
@@ -1928,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();
@@ -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', () => {
@@ -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: {
@@ -149,7 +149,7 @@ describe('POST /group', () => {
).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cannotCreatePublicGuildWhenMuted'),
message: t('chatPrivilegesRevoked'),
});
});
});
@@ -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'),
});
});
@@ -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);
});
});
@@ -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',
});
});
@@ -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();
});
@@ -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();
});
});
@@ -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}`);
@@ -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}`);
@@ -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({
@@ -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`);
@@ -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}`);
@@ -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}`);
@@ -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);
});
});
@@ -83,12 +83,12 @@ context('avatar.vue', () => {
expect(vm.paddingTop).to.equal('28px');
});
it('is 24.5px if user has a pet', () => {
it('is 24px if user has a pet', () => {
vm.member.items = {
currentPet: { name: 'Foo' },
};
expect(vm.paddingTop).to.equal('24.5px');
expect(vm.paddingTop).to.equal('24px');
});
it('is 0px if user has a mount', () => {
@@ -297,4 +297,4 @@ context('avatar.vue', () => {
});
});
});
});
});
+12
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
+60 -1
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();
}
});
});
+12 -1
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;
});
+42 -1
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;
+18
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;
+34
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);
});
});
});
});
+18
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);
});
});
+11
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;
+3 -2
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', () => {
+14 -1
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', () => {
+1
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();
+1
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();
+6
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);
}
+7
View File
@@ -12,6 +12,8 @@ div
banned-account-modal
amazon-payments-modal(v-if='!isStaticPage')
payments-success-modal
sub-cancel-modal-confirm(v-if='isUserLoaded')
sub-canceled-modal(v-if='isUserLoaded')
snackbars
router-view(v-if="!isUserLoggedIn || isStaticPage")
template(v-else)
@@ -187,6 +189,8 @@ import notifications from 'client/mixins/notifications';
import { setup as setupPayments } from 'client/libs/payments';
import amazonPaymentsModal from 'client/components/payments/amazonModal';
import paymentsSuccessModal from 'client/components/payments/successModal';
import subCancelModalConfirm from 'client/components/payments/cancelModalConfirm';
import subCanceledModal from 'client/components/payments/canceledModal';
import spellsMixin from 'client/mixins/spells';
import { CONSTANTS, getLocalSetting, removeLocalSetting } from 'client/libs/userlocalManager';
@@ -210,6 +214,8 @@ export default {
amazonPaymentsModal,
bannedAccountModal,
paymentsSuccessModal,
subCancelModalConfirm,
subCanceledModal,
},
data () {
return {
@@ -650,5 +656,6 @@ export default {
<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>
+6
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;
@@ -1,72 +1,78 @@
.promo_april_fools_2019 {
.promo_armoire_backgrounds_201906 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -719px -767px;
background-position: -424px -720px;
width: 423px;
height: 147px;
}
.promo_armoire_backgrounds_201903 {
.promo_bronze_quest {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -933px;
width: 423px;
height: 147px;
background-position: 0px -359px;
width: 360px;
height: 360px;
}
.promo_beffymaroo_wondercon {
.promo_dolphin_quest {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 718px;
height: 932px;
width: 553px;
height: 358px;
}
.promo_celestial_rainbow_potions {
.promo_glass_watery_potions {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -933px;
background-position: 0px -720px;
width: 423px;
height: 147px;
}
.promo_classes_spring2019 {
.promo_halfmoon_glasses {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -719px -604px;
width: 432px;
height: 162px;
}
.promo_egg_hunt {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1160px 0px;
width: 354px;
background-position: -707px -868px;
width: 279px;
height: 147px;
}
.promo_mystery_201903 {
.promo_mystery_201905 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1160px -148px;
width: 351px;
background-position: -424px -868px;
width: 282px;
height: 147px;
}
.promo_seasonalshop_spring {
.promo_oddballs_bundle {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1160px -492px;
background-position: 0px -868px;
width: 423px;
height: 147px;
}
.promo_orcas {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -848px -720px;
width: 219px;
height: 147px;
}
.promo_seasonal_shop {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -867px -431px;
width: 162px;
height: 138px;
height: 132px;
}
.promo_summer_splash_2019 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -554px -244px;
width: 408px;
height: 186px;
}
.promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1401px -296px;
background-position: -963px -244px;
width: 96px;
height: 69px;
}
.scene_dailies {
.scene_casting_spells {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -719px -327px;
width: 327px;
height: 276px;
background-position: -554px -431px;
width: 312px;
height: 222px;
}
.scene_tavern {
.scene_positivity {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -719px 0px;
width: 440px;
height: 326px;
}
.scene_todos {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1160px -296px;
width: 240px;
height: 195px;
background-position: -554px 0px;
width: 531px;
height: 243px;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,918 @@
.Pet-TRex-Base {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -328px 0px;
width: 81px;
height: 99px;
}
.Pet-TRex-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -328px -100px;
width: 81px;
height: 99px;
}
.Pet-TRex-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -328px -200px;
width: 81px;
height: 99px;
}
.Pet-TRex-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: 0px -300px;
width: 81px;
height: 99px;
}
.Pet-TRex-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -82px -300px;
width: 81px;
height: 99px;
}
.Pet-TRex-Red {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -164px -300px;
width: 81px;
height: 99px;
}
.Pet-TRex-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -246px -300px;
width: 81px;
height: 99px;
}
.Pet-TRex-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -328px -300px;
width: 81px;
height: 99px;
}
.Pet-TRex-White {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -410px 0px;
width: 81px;
height: 99px;
}
.Pet-TRex-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -410px -100px;
width: 81px;
height: 99px;
}
.Pet-TigerCub-White {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -82px 0px;
width: 81px;
height: 99px;
}
.Pet-TigerCub-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -492px -700px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Base {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -164px 0px;
width: 81px;
height: 99px;
}
.Pet-Treeling-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: 0px -100px;
width: 81px;
height: 99px;
}
.Pet-Treeling-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -82px -100px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -164px -100px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -246px 0px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Red {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -246px -100px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: 0px -200px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -82px -200px;
width: 81px;
height: 99px;
}
.Pet-Treeling-White {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -164px -200px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -246px -200px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Base {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -410px -200px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -410px -300px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -492px 0px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -492px -100px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -492px -200px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Red {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -492px -300px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: 0px -400px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -82px -400px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-White {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -164px -400px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -246px -400px;
width: 81px;
height: 99px;
}
.Pet-Turkey-Base {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -328px -400px;
width: 81px;
height: 99px;
}
.Pet-Turkey-Gilded {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -410px -400px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Base {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -492px -400px;
width: 81px;
height: 99px;
}
.Pet-Turtle-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -574px 0px;
width: 81px;
height: 99px;
}
.Pet-Turtle-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -574px -100px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -574px -200px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -574px -300px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Red {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -574px -400px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: 0px -500px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -82px -500px;
width: 81px;
height: 99px;
}
.Pet-Turtle-White {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -164px -500px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -246px -500px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Base {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -328px -500px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -410px -500px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -492px -500px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -574px -500px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -656px 0px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Red {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -656px -100px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -656px -200px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -656px -300px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-White {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -656px -400px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -656px -500px;
width: 81px;
height: 99px;
}
.Pet-Velociraptor-Base {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: 0px -600px;
width: 81px;
height: 99px;
}
.Pet-Velociraptor-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -82px -600px;
width: 81px;
height: 99px;
}
.Pet-Velociraptor-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -164px -600px;
width: 81px;
height: 99px;
}
.Pet-Velociraptor-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -246px -600px;
width: 81px;
height: 99px;
}
.Pet-Velociraptor-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -328px -600px;
width: 81px;
height: 99px;
}
.Pet-Velociraptor-Red {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -410px -600px;
width: 81px;
height: 99px;
}
.Pet-Velociraptor-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -492px -600px;
width: 81px;
height: 99px;
}
.Pet-Velociraptor-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -574px -600px;
width: 81px;
height: 99px;
}
.Pet-Velociraptor-White {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -656px -600px;
width: 81px;
height: 99px;
}
.Pet-Velociraptor-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -738px 0px;
width: 81px;
height: 99px;
}
.Pet-Whale-Base {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -738px -100px;
width: 81px;
height: 99px;
}
.Pet-Whale-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -738px -200px;
width: 81px;
height: 99px;
}
.Pet-Whale-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -738px -300px;
width: 81px;
height: 99px;
}
.Pet-Whale-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -738px -400px;
width: 81px;
height: 99px;
}
.Pet-Whale-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -738px -500px;
width: 81px;
height: 99px;
}
.Pet-Whale-Red {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -738px -600px;
width: 81px;
height: 99px;
}
.Pet-Whale-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: 0px -700px;
width: 81px;
height: 99px;
}
.Pet-Whale-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -82px -700px;
width: 81px;
height: 99px;
}
.Pet-Whale-White {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -164px -700px;
width: 81px;
height: 99px;
}
.Pet-Whale-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -246px -700px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Aquatic {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -328px -700px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Base {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -410px -700px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Bronze {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: 0px 0px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Celestial {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -574px -700px;
width: 81px;
height: 99px;
}
.Pet-Wolf-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -656px -700px;
width: 81px;
height: 99px;
}
.Pet-Wolf-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -738px -700px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Cupid {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -820px 0px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -820px -100px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Ember {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -820px -200px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Fairy {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -820px -300px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Floral {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -820px -400px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Frost {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -820px -500px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Ghost {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -820px -600px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Glass {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -820px -700px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Glow {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: 0px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -82px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Holly {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -164px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-IcySnow {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -246px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Peppermint {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -328px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Rainbow {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -410px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Red {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -492px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-RoseQuartz {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -574px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-RoyalPurple {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -656px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -738px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Shimmer {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -820px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -902px 0px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Spooky {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -902px -100px;
width: 81px;
height: 99px;
}
.Pet-Wolf-StarryNight {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -902px -200px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Sunshine {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -902px -300px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Thunderstorm {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -902px -400px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Veggie {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -902px -500px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Veteran {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -902px -600px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Watery {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -902px -700px;
width: 81px;
height: 99px;
}
.Pet-Wolf-White {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -902px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -984px 0px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Base {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -984px -100px;
width: 81px;
height: 99px;
}
.Pet-Yarn-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -984px -200px;
width: 81px;
height: 99px;
}
.Pet-Yarn-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -984px -300px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -984px -400px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -984px -500px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Red {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -984px -600px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -984px -700px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -984px -800px;
width: 81px;
height: 99px;
}
.Pet-Yarn-White {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: 0px -900px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -82px -900px;
width: 81px;
height: 99px;
}
.Pet_HatchingPotion_Aquatic {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -233px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Base {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -276px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Bronze {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -302px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Celestial {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -371px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -440px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -509px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Cupid {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -578px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Desert {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -647px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Ember {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -716px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Fairy {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -785px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Floral {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -854px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Frost {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -923px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Ghost {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -992px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Glass {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px 0px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Glow {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -69px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Golden {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -138px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Holly {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -207px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_IcySnow {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -164px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Peppermint {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -345px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Purple {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -414px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Rainbow {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -483px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Red {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -552px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_RoseQuartz {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -621px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_RoyalPurple {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -690px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Shade {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -759px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Shimmer {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -828px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -1066px -897px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Spooky {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: 0px -1000px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_StarryNight {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -69px -1000px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Sunshine {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -138px -1000px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Thunderstorm {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -207px -1000px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Watery {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -276px -1000px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_White {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -345px -1000px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-25.png');
background-position: -414px -1000px;
width: 68px;
height: 68px;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 983 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 KiB

After

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 KiB

After

Width:  |  Height:  |  Size: 592 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 124 KiB

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