Compare commits

..

155 Commits

Author SHA1 Message Date
Sabe Jones 581a40ffad 4.104.1 2019-07-17 14:20:35 -05:00
Sabe Jones 61c78355bd feat(content): enable Splashy Pals Bundle 2019-07-17 14:20:21 -05:00
Matteo Pagliazzi f82c93a9ec restore default for migration runner and respect unsubscription preferences 2019-07-17 21:07:43 +02:00
Matteo Pagliazzi e8c6340af8 add migration to restore scrolls 2019-07-17 20:48:48 +02:00
Matteo Pagliazzi 1e66cc9193 fix 2019-07-17 15:11:58 +02:00
Matteo Pagliazzi ef6d835391 fix 2019-07-17 14:34:03 +02:00
Matteo Pagliazzi 51f7ce3280 possible fix for script stuck script 2019-07-17 13:34:08 +02:00
Sabe Jones 2b7891e788 WIP(migration): then 2019-07-17 06:05:38 -05:00
Sabe Jones c640a648b9 WIP(migration): refactor group fix script 2019-07-17 10:42:08 +00:00
Sabe Jones 116f0446e5 fix(migrations): don't default to mystery items 2019-07-16 22:55:59 -05:00
Sabe Jones 065e8303df fix(groups): migration to restore disrupted user data 2019-07-16 20:23:05 -05:00
Sabe Jones 20fc233ad7 4.104.0 2019-07-02 13:30:46 -05:00
Sabe Jones adbe58a0da chore(sprites): compile 2019-07-02 13:30:41 -05:00
Sabe Jones b1b90d6dba feat(content): backgrounds and Armoire items 2019-07-02 13:30:33 -05:00
Sabe Jones ef29247526 4.103.1 2019-07-01 14:54:03 -05:00
Sabe Jones 1c59e78ea9 chore(sprites): compile 2019-07-01 14:53:50 -05:00
Sabe Jones 18e4e11a95 chore(news): Bailey
and extend a couple of items by 1 day
2019-07-01 14:53:40 -05:00
SabreCat 82f34ed437 v4.103.0 2019-06-25 15:19:00 +00:00
SabreCat 3b268287b1 chore(sprites): compile 2019-06-25 15:14:56 +00:00
SabreCat 0e253fbb06 feat(content): mystery items 2019-06-25 15:13:08 +00:00
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 664cf5a47b Merge branch 'release' into develop 2019-05-21 15:29:40 -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
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
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 90532b0763 Merge branch 'develop' into Yutsuten/party-chat-translations 2019-05-08 15:13:53 -05:00
Ian Oxley 6fba71ea2c Add :focus styles to match :hover styles
Add :focus styles to the .habitica-menu-dropdown.
These match the existing :hover styles.
2019-03-28 20:38:17 +00:00
Ian Oxley 5755bfc952 Merge branch 'develop' into navbar-a11y-alt-text 2019-03-28 20:19:04 +00:00
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
477 changed files with 32713 additions and 29712 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
}
};
@@ -0,0 +1,84 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20190716_groups_fix';
import monk from 'monk';
import nconf from 'nconf';
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
let backupUsers;
async function updateUser (user) {
count++;
let set = { migration: MIGRATION_NAME };
let addToSet;
const monkPromise = new Promise((resolve, reject) => {
backupUsers.findOne(
{ _id: user._id },
{ fields: { _id: 1, party: 1, guilds: 1 }},
).then(foundUserInBackup => {
resolve(foundUserInBackup);
}).catch(e => {
reject(e);
})
});
let backupUser = await monkPromise;
if (!backupUser) return;
if (!user.party._id) {
set.party = backupUser.party;
}
addToSet = { guilds: { $each: backupUser.guilds }};
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return User.update({ _id: user._id }, { $set: set, $addToSet: addToSet }).exec();
}
module.exports = async function processUsers () {
const query = {
'auth.timestamps.loggedin': {$gt: new Date('2019-07-15')},
};
let backupDb = monk(CONNECTION_STRING);
const backupDbPromise = new Promise((resolve, reject) => {
backupDb.then(() => resolve()).catch((e) => reject(e));
});
await backupDbPromise;
console.log('Connected to backup db');
backupUsers = backupDb.get('users', { castIds: false });
const fields = {
_id: 1,
party: 1,
guilds: 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,86 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20190717_groups_fix_2';
import monk from 'monk';
import nconf from 'nconf';
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
import { model as User } from '../../../website/server/models/user';
import { sendTxn as sendTxnEmail } from '../../../website/server/libs/email';
import shared from '../../../website/common';
const questScrolls = shared.content.quests;
const progressCount = 1000;
let count = 0;
let backupUsers;
async function updateGroup (group) {
count++;
if (group && group.quest && group.quest.leader) {
const quest = questScrolls[group.quest.key];
const leader = await User.findOne({_id: group.quest.leader}).exec();
if (!leader) return;
await User.update({ _id: leader._id }, {
$set: {migration: MIGRATION_NAME},
$inc: {
balance: 1,
[`items.quests.${group.quest.key}`]: 1,
},
}).exec();
// unsubscribe from all is already checked by sendTxnEmail
if (leader.preferences && leader.preferences.emailNotifications && leader.preferences.emailNotifications.majorUpdates !== false) {
sendTxnEmail(leader, 'groups-outage');
}
}
if (count % progressCount === 0) console.warn(`${count} ${group._id}`);
}
module.exports = async function processUsers () {
const query = {
type: 'party'
};
let backupDb = monk(CONNECTION_STRING);
const backupDbPromise = new Promise((resolve, reject) => {
backupDb.then(() => resolve()).catch((e) => reject(e));
});
await backupDbPromise;
console.log('Connected to backup db');
const backupGroups = backupDb.get('groups', { castIds: false });
while (true) { // eslint-disable-line no-constant-condition
const groupsPromise = new Promise((resolve, reject) => {
backupGroups
.find(query, {
limit: 250,
sort: {_id: 1}
})
.then(foundGroupInBackup => {
resolve(foundGroupInBackup);
}).catch(e => {
reject(e);
})
});
const groups = await groupsPromise;
if (groups.length === 0) {
console.warn('All appropriate groups found and modified.');
console.warn(`\n${count} groups processed\n`);
break;
} else {
query._id = {
$gt: groups[groups.length - 1],
};
}
await Promise.all(groups.map(updateGroup)); // eslint-disable-line no-await-in-loop
}
};
+1 -1
View File
@@ -17,7 +17,7 @@ function setUpServer () {
setUpServer();
// Replace this with your migration
const processUsers = require('./users/mystery-items.js');
const processUsers = require('');
processUsers()
.then(function success () {
process.exit(0);
+2 -2
View File
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
const MIGRATION_NAME = 'mystery_items_201904';
const MYSTERY_ITEMS = ['armor_mystery_201904', 'head_mystery_201904'];
const MIGRATION_NAME = 'mystery_items_201906';
const MYSTERY_ITEMS = ['headAccessory_mystery_201906', 'armor_mystery_201906'];
import { model as User } from '../../website/server/models/user';
import { model as UserNotification } from '../../website/server/models/userNotification';
+271 -500
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.96.1",
"version": "4.104.1",
"main": "./website/server/index.js",
"dependencies": {
"@google-cloud/trace-agent": "^3.6.0",
@@ -14,7 +14,7 @@
"apn": "^2.2.0",
"autoprefixer": "^9.4.0",
"aws-sdk": "^2.432.0",
"axios": "^0.18.0",
"axios": "^0.19.0",
"axios-progress-bar": "^1.2.0",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
@@ -48,7 +48,7 @@
"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",
@@ -58,9 +58,9 @@
"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",
@@ -71,7 +71,7 @@
"node-sass": "^4.9.0",
"nodemailer": "^6.0.0",
"ora": "^3.2.0",
"pageres": "^4.1.1",
"pageres": "^5.1.0",
"passport": "^0.4.0",
"passport-facebook": "^2.0.0",
"passport-google-oauth20": "1.0.0",
@@ -98,7 +98,7 @@
"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.10",
"vue-loader": "^14.2.2",
@@ -152,7 +152,7 @@
"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.3",
"cross-spawn": "^6.0.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', () => {
@@ -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'),
});
});
@@ -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();
});
@@ -141,7 +141,14 @@ describe('POST /groups/:groupId/quests/cancel', () => {
members: {},
});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWithMatch(/cancelled the party quest Wail of the Whale.`/);
expect(Group.prototype.sendChat).to.be.calledWithMatch({
message: sinon.match(/cancelled the party quest Wail of the Whale.`/),
info: {
quest: 'whale',
type: 'quest_cancel',
user: sinon.match.any,
},
});
stub.restore();
});
@@ -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', () => {
});
});
});
});
});
+7
View File
@@ -80,6 +80,13 @@ describe('shared.ops.buy', () => {
headAccessory_special_redHeadband: true,
headAccessory_special_whiteHeadband: true,
headAccessory_special_yellowHeadband: true,
eyewear_special_blackHalfMoon: true,
eyewear_special_blueHalfMoon: true,
eyewear_special_greenHalfMoon: true,
eyewear_special_pinkHalfMoon: true,
eyewear_special_redHalfMoon: true,
eyewear_special_whiteHalfMoon: true,
eyewear_special_yellowHalfMoon: true,
});
});
+7
View File
@@ -75,6 +75,13 @@ describe('shared.ops.buyMarketGear', () => {
headAccessory_special_redHeadband: true,
headAccessory_special_whiteHeadband: true,
headAccessory_special_yellowHeadband: true,
eyewear_special_blackHalfMoon: true,
eyewear_special_blueHalfMoon: true,
eyewear_special_greenHalfMoon: true,
eyewear_special_pinkHalfMoon: true,
eyewear_special_redHalfMoon: true,
eyewear_special_whiteHalfMoon: true,
eyewear_special_yellowHalfMoon: true,
});
expect(analytics.track).to.be.calledOnce;
});
+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;
+18
View File
@@ -159,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);
});
});
});
});
+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;
+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();
+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);
}
+1
View File
@@ -656,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>
@@ -1,66 +1,78 @@
.promo_armoire_backgrounds_201905 {
.promo_armoire_backgrounds_201907 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -786px;
background-position: -313px -524px;
width: 423px;
height: 147px;
}
.promo_bronze_quest {
.promo_glass_watery_potions {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -499px 0px;
width: 360px;
height: 360px;
}
.promo_feathered_friends_bundle {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -638px;
background-position: -854px 0px;
width: 423px;
height: 147px;
}
.promo_floral_sunshine_potions {
.promo_mystery_201906 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -638px;
width: 423px;
height: 147px;
}
.promo_mystery_201904 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -860px -223px;
background-position: -854px -296px;
width: 282px;
height: 147px;
}
.promo_orcas {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -854px -444px;
width: 219px;
height: 147px;
}
.promo_seafoam {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -376px -337px;
width: 425px;
height: 148px;
}
.promo_seasonal_shop {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1074px -444px;
width: 162px;
height: 132px;
}
.promo_splashy_pals_bundle {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -854px -148px;
width: 423px;
height: 147px;
}
.promo_splashy_skins {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -337px;
width: 375px;
height: 186px;
}
.customize-option.promo_splashy_skins {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -25px -352px;
width: 60px;
height: 60px;
}
.promo_summer_splash_2019 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -445px 0px;
width: 408px;
height: 186px;
}
.promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -1068px -371px;
background-position: -1137px -296px;
width: 96px;
height: 69px;
}
.scene_gold {
.scene_casting_spells {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 498px;
height: 360px;
}
.scene_languages {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -328px -361px;
width: 297px;
height: 261px;
}
.scene_rewards {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -860px -371px;
width: 207px;
height: 180px;
}
.scene_spells {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -860px 0px;
background-position: 0px -524px;
width: 312px;
height: 222px;
}
.scene_yesterdailies_repeatables {
.scene_hat_guild {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -361px;
width: 327px;
height: 276px;
background-position: 0px 0px;
width: 444px;
height: 336px;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 561 KiB

After

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 580 KiB

After

Width:  |  Height:  |  Size: 610 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: 309 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 KiB

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 112 KiB

+1 -3
View File
@@ -35,6 +35,7 @@ $maroon-50: #C92B2B;
$maroon-100: #DE3F3F;
$maroon-500: #F19595;
$yellow-5: #EE9109;
$yellow-10: #FFA623;
$yellow-50: #FFB445;
$yellow-100: #FFBE5D;
@@ -60,9 +61,6 @@ $green-50: #3FDAA2;
$green-100: #5AEAB2;
$green-500: #A6FFDF;
$orange-10: #ee9109;
$orange-50: #bf7d1a;
$suggested-item-color: #D5C8FF;
$healer-color: #cf8229;
+5 -5
View File
@@ -2,8 +2,8 @@
// possible values are: normal, fall, habitoween, thanksgiving, winter, nye, birthday, valentines, spring, summer
// more to be added on future seasons
$npc_market_flavor: 'normal';
$npc_quests_flavor: 'normal';
$npc_seasonal_flavor: 'normal';
$npc_timetravelers_flavor: 'normal';
$npc_tavern_flavor: 'normal';
$npc_market_flavor: 'summer';
$npc_quests_flavor: 'summer';
$npc_seasonal_flavor: 'summer';
$npc_timetravelers_flavor: 'summer';
$npc_tavern_flavor: 'summer';
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="9" viewBox="0 0 14 9">
<path fill="none" fill-rule="evenodd" stroke="#BDA8FF" stroke-width="2.5" d="M13 1L7 7 1 1"/>
</svg>

After

Width:  |  Height:  |  Size: 187 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd">
<path fill="#FFF" d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0m0 2c3.308 0 6 2.692 6 6s-2.692 6-6 6-6-2.692-6-6 2.692-6 6-6"/>
<path stroke="#FFF" stroke-linecap="round" stroke-width="2" d="M8 5v3.031L10 10"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 358 B

+1 -1
View File
@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="#E1E0E3" fill-rule="evenodd" d="M8 0c2.173 0 2.445.01 3.298.048.852.04 1.433.174 1.942.372.526.205.973.478 1.418.922.444.445.717.892.922 1.418.198.509.333 1.09.372 1.942C15.99 5.555 16 5.827 16 8s-.01 2.445-.048 3.298c-.04.852-.174 1.433-.372 1.942a3.924 3.924 0 0 1-.922 1.418 3.924 3.924 0 0 1-1.418.922c-.509.198-1.09.333-1.942.372-.853.04-1.125.048-3.298.048s-2.445-.009-3.298-.048c-.852-.04-1.433-.174-1.942-.372a3.924 3.924 0 0 1-1.418-.922A3.924 3.924 0 0 1 .42 13.24c-.198-.509-.333-1.09-.372-1.942C.01 10.445 0 10.173 0 8s.01-2.445.048-3.298C.088 3.85.222 3.269.42 2.76c.205-.526.478-.973.922-1.418A3.924 3.924 0 0 1 2.76.42C3.269.222 3.85.087 4.702.048 5.555.01 5.827 0 8 0zm0 3.892a4.108 4.108 0 1 0 0 8.216 4.108 4.108 0 0 0 0-8.216zm5.23-.162a.96.96 0 1 0-1.92 0 .96.96 0 0 0 1.92 0zM8 10.667a2.666 2.666 0 1 1 0-5.333 2.666 2.666 0 0 1 0 5.333z"/>
<path fill-rule="evenodd" d="M8 0c2.173 0 2.445.01 3.298.048.852.04 1.433.174 1.942.372.526.205.973.478 1.418.922.444.445.717.892.922 1.418.198.509.333 1.09.372 1.942C15.99 5.555 16 5.827 16 8s-.01 2.445-.048 3.298c-.04.852-.174 1.433-.372 1.942a3.924 3.924 0 0 1-.922 1.418 3.924 3.924 0 0 1-1.418.922c-.509.198-1.09.333-1.942.372-.853.04-1.125.048-3.298.048s-2.445-.009-3.298-.048c-.852-.04-1.433-.174-1.942-.372a3.924 3.924 0 0 1-1.418-.922A3.924 3.924 0 0 1 .42 13.24c-.198-.509-.333-1.09-.372-1.942C.01 10.445 0 10.173 0 8s.01-2.445.048-3.298C.088 3.85.222 3.269.42 2.76c.205-.526.478-.973.922-1.418A3.924 3.924 0 0 1 2.76.42C3.269.222 3.85.087 4.702.048 5.555.01 5.827 0 8 0zm0 3.892a4.108 4.108 0 1 0 0 8.216 4.108 4.108 0 0 0 0-8.216zm5.23-.162a.96.96 0 1 0-1.92 0 .96.96 0 0 0 1.92 0zM8 10.667a2.666 2.666 0 1 1 0-5.333 2.666 2.666 0 0 1 0 5.333z"/>
</svg>

Before

Width:  |  Height:  |  Size: 969 B

After

Width:  |  Height:  |  Size: 954 B

@@ -164,30 +164,31 @@ export default {
classGear (heroClass) {
if (heroClass === 'rogue') {
return {
armor: 'armor_rogue_5',
head: 'head_rogue_5',
shield: 'shield_rogue_6',
weapon: 'weapon_rogue_6',
armor: 'armor_special_summer2019Rogue',
head: 'head_special_summer2019Rogue',
shield: 'shield_special_summer2019Rogue',
weapon: 'weapon_special_summer2019Rogue',
};
} else if (heroClass === 'wizard') {
return {
armor: 'armor_wizard_5',
head: 'head_wizard_5',
weapon: 'weapon_wizard_6',
armor: 'armor_special_summer2019Mage',
head: 'head_special_summer2019Mage',
shield: 'shield_special_summer2019Mage',
weapon: 'weapon_special_summer2019Mage',
};
} else if (heroClass === 'healer') {
return {
armor: 'armor_healer_5',
head: 'head_healer_5',
shield: 'shield_healer_5',
weapon: 'weapon_healer_6',
armor: 'armor_special_summer2019Healer',
head: 'head_special_summer2019Healer',
shield: 'shield_special_summer2019Healer',
weapon: 'weapon_special_summer2019Healer',
};
} else {
return {
armor: 'armor_warrior_5',
head: 'head_warrior_5',
shield: 'shield_warrior_5',
weapon: 'weapon_warrior_6',
armor: 'armor_special_summer2019Warrior',
head: 'head_special_summer2019Warrior',
shield: 'shield_special_summer2019Warrior',
weapon: 'weapon_special_summer2019Warrior',
};
}
},
@@ -0,0 +1,42 @@
<template lang="pug">
b-modal#generic-achievement(:title='data.message', size='md', :hide-footer='true')
.modal-body
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
p(v-html='data.modalText')
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import {mapState} from 'client/libs/store';
export default {
components: {
achievementFooter,
achievementAvatar,
},
props: ['data'],
computed: {
...mapState({user: 'user.data'}),
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'generic-achievement');
},
},
};
</script>
@@ -0,0 +1,46 @@
<template lang="pug">
b-modal#just-add-water(:title='title', size='md', :hide-footer='true')
.modal-body
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
p {{ $t('achievementJustAddWaterModalText') }}
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import {mapState} from 'client/libs/store';
export default {
components: {
achievementFooter,
achievementAvatar,
},
computed: {
...mapState({user: 'user.data'}),
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementJustAddWater')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'just-add-water');
},
},
};
</script>
@@ -0,0 +1,46 @@
<template lang="pug">
b-modal#lost-masterclasser(:title='title', size='md', :hide-footer='true')
.modal-body
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
p {{ $t('achievementLostMasterclasserModalText') }}
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import {mapState} from 'client/libs/store';
export default {
components: {
achievementFooter,
achievementAvatar,
},
computed: {
...mapState({user: 'user.data'}),
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementLostMasterclasser')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'lost-masterclasser');
},
},
};
</script>
@@ -0,0 +1,46 @@
<template lang="pug">
b-modal#mind-over-matter(:title='title', size='md', :hide-footer='true')
.modal-body
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
p {{ $t('achievementMindOverMatterModalText') }}
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import {mapState} from 'client/libs/store';
export default {
components: {
achievementFooter,
achievementAvatar,
},
computed: {
...mapState({user: 'user.data'}),
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementMindOverMatter')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'mind-over-matter');
},
},
};
</script>
+1 -1
View File
@@ -139,7 +139,7 @@ export default {
let val = '28px';
if (!this.avatarOnly) {
if (this.member.items.currentPet) val = '24.5px';
if (this.member.items.currentPet) val = '24px';
if (this.member.items.currentMount) val = '0px';
}
+12 -4
View File
@@ -87,7 +87,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
button.btn.btn-secondary.purchase-all(@click='unlock(`skin.${set.keys.join(",skin.")}`)') {{ $t('purchaseAll') }}
#hair.section.customize-section(v-if='activeTopPage === "hair"')
.row.col-12.sub-menu.text-center
.col-3.text-center.sub-menu-item(@click='changeSubPage("color")', :class='{active: activeSubPage === "color"}')
.col-3.text-center.sub-menu-item(@click='changeSubPage("color")', :class='{active: activeSubPage === "color", "offset-2": !editing}')
strong(v-once) {{$t('color')}}
.col-3.text-center.sub-menu-item(@click='changeSubPage("bangs")', :class='{active: activeSubPage === "bangs"}')
strong(v-once) {{$t('bangs')}}
@@ -139,6 +139,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
span 5
button.btn.btn-secondary.purchase-all(@click='unlock(`hair.base.${baseHair4Keys.join(",hair.base.")}`)') {{ $t('purchaseAll') }}
.col-12.customize-options
.head_0.option(v-if="!editing", @click='set({"preferences.hair.base": 0})', :class="[{ active: user.preferences.hair.base === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
.option(v-for='option in baseHair1',
:class='{active: user.preferences.hair.base === option}')
.base.sprite.customize-option(:class="`hair_base_${option}_${user.preferences.hair.color}`", @click='set({"preferences.hair.base": option})')
@@ -377,8 +378,8 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
username-form(@usernameConfirmed='modalPage += 1', :avatarIntro='true')
.small.text-center(v-html="$t('usernameTOSRequirements')")
.section.container.footer
.row(v-if='!editing && !(modalPage === 1)')
.section.container.footer(v-if='!editing && !(modalPage === 1)')
.row
.col-3.offset-1.text-center
div(v-if='modalPage > 1', @click='prev()')
.prev-arrow
@@ -406,6 +407,10 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
#avatar-modal___BV_modal_body_, #avatar-modal___BV_modal_body_ {
padding: 0;
}
#avatar-modal .modal-dialog {
margin-top: 7rem;
}
</style>
<style lang="scss" scoped>
@@ -1125,7 +1130,10 @@ export default {
return options;
},
eyewear () {
let keys = ['blackTopFrame', 'blueTopFrame', 'greenTopFrame', 'pinkTopFrame', 'redTopFrame', 'whiteTopFrame', 'yellowTopFrame'];
let keys = [
'blackTopFrame', 'blueTopFrame', 'greenTopFrame', 'pinkTopFrame', 'redTopFrame', 'whiteTopFrame', 'yellowTopFrame',
'blackHalfMoon', 'blueHalfMoon', 'greenHalfMoon', 'pinkHalfMoon', 'redHalfMoon', 'whiteHalfMoon', 'yellowHalfMoon',
];
let options = keys.map(key => {
let newKey = `eyewear_special_${key}`;
let option = {};
@@ -25,7 +25,7 @@ router-link.card-link(:to="{ name: 'guild', params: { groupId: guild._id } }")
div.guild-bank(v-if='displayGemBank', v-once) {{$t('guildBank')}}
.row
category-tags.col-md-12(:categories="guild.categories", :owner="isOwner", v-once)
span.recommend-text(v-if='showSuggested(guild._id)') Suggested because youre new to Habitica.
span.recommend-text(v-if='showSuggested(guild._id)') {{$t('suggestedGroup')}}
</template>
<style lang="scss" scoped>
@@ -175,8 +175,9 @@ export default {
methods: {
showSuggested (guildId) {
let habiticaHelpingGuildId = '5481ccf3-5d2d-48a9-a871-70a7380cee5a';
let createdAfterRedesign = moment(this.user.auth.timestamps.created).isAfter('2017-08-01');
return guildId === habiticaHelpingGuildId && createdAfterRedesign;
let sixtyDaysAgoFromNow = moment().subtract(60, 'days');
let isUserNew = moment(this.user.auth.timestamps.created).isAfter(sixtyDaysAgoFromNow);
return guildId === habiticaHelpingGuildId && isUserNew;
},
async join () {
// @TODO: This needs to be in the notifications where users will now accept invites
@@ -35,7 +35,7 @@ sidebar-section(:title="$t('questDetailsTitle')")
.grey-progress-bar
.collect-progress-bar(:style="{width: (group.quest.progress.collect[key] / value.count) * 100 + '%'}")
strong {{group.quest.progress.collect[key]}} / {{value.count}}
div.text-right {{parseFloat(user.party.quest.progress.collectedItems) || 0}} items found
div.text-right(v-if='userIsOnQuest') {{parseFloat(user.party.quest.progress.collectedItems) || 0}} items found
.boss-info(v-if='questData.boss')
.row
.col-6
+120 -19
View File
@@ -6,49 +6,61 @@ div
report-flag-modal
send-gems-modal
b-navbar.topbar.navbar-inverse.static-top(toggleable="lg", type="dark", :class="navbarZIndexClass")
b-navbar-brand.brand
b-navbar-brand.brand(aria-label="Habitica")
.logo.svg-icon.d-none.d-xl-block(v-html="icons.logo")
.svg-icon.gryphon.d-xs-block.d-xl-none
b-navbar-toggle(target='menu_collapse').menu-toggle
.quick-menu.mobile-only.form-inline
a.item-with-icon(@click="sync", v-b-tooltip.hover.bottom="$t('sync')")
a.item-with-icon(@click="sync", v-b-tooltip.hover.bottom="$t('sync')", :aria-label="$t('sync')")
.top-menu-icon.svg-icon(v-html="icons.sync")
notification-menu.item-with-icon
user-dropdown.item-with-icon
b-collapse#menu_collapse.collapse.navbar-collapse
b-collapse#menu_collapse(v-model="menuIsOpen").collapse.navbar-collapse
b-navbar-nav.menu-list
b-nav-item.topbar-item(tag="li", :to="{name: 'tasks'}", exact) {{ $t('tasks') }}
li.topbar-item(:class="{'active': $route.path.startsWith('/inventory')}")
b-nav-item.topbar-item(:class="{'active': $route.path === '/'}" tag="li", :to="{name: 'tasks'}", exact) {{ $t('tasks') }}
li.topbar-item(:class="{'active': $route.path.startsWith('/inventory'), 'down': $route.path.startsWith('/inventory') && this.isDesktop()}").droppable
.chevron.rotate(@click='dropdownMobile($event)')
.chevron-icon-down(v-html="icons.chevronDown", v-once)
router-link.nav-link(:to="{name: 'items'}") {{ $t('inventory') }}
.topbar-dropdown
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'items'}", exact) {{ $t('items') }}
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'equipment'}") {{ $t('equipment') }}
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'stable'}") {{ $t('stable') }}
li.topbar-item(:class="{'active': $route.path.startsWith('/shop')}")
li.topbar-item(:class="{'active': $route.path.startsWith('/shop'), 'down': $route.path.startsWith('/shop') && this.isDesktop()}").droppable
.chevron.rotate(@click='dropdownMobile($event)')
.chevron-icon-down(v-html="icons.chevronDown", v-once)
router-link.nav-link(:to="{name: 'market'}") {{ $t('shops') }}
.topbar-dropdown
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'market'}", exact) {{ $t('market') }}
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'quests'}") {{ $t('quests') }}
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'seasonal'}") {{ $t('titleSeasonalShop') }}
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'time'}") {{ $t('titleTimeTravelers') }}
b-nav-item.topbar-item(tag="li", :to="{name: 'party'}", v-if='this.user.party._id') {{ $t('party') }}
b-nav-item.topbar-item(@click='openPartyModal()', v-if='!this.user.party._id') {{ $t('party') }}
li.topbar-item(:class="{'active': $route.path.startsWith('/guilds')}")
b-nav-item.topbar-item(:class="{'active': $route.path.startsWith('/party')}" tag="li", :to="{name: 'party'}", v-if='this.user.party._id') {{ $t('party') }}
b-nav-item.topbar-item(:class="{'active': $route.path.startsWith('/party')}" @click='openPartyModal()', v-if='!this.user.party._id') {{ $t('party') }}
li.topbar-item(:class="{'active': $route.path.startsWith('/groups'), 'down': $route.path.startsWith('/groups') && this.isDesktop()}").droppable
.chevron.rotate(@click='dropdownMobile($event)')
.chevron-icon-down(v-html="icons.chevronDown", v-once)
router-link.nav-link(:to="{name: 'tavern'}") {{ $t('guilds') }}
.topbar-dropdown
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'tavern'}") {{ $t('tavern') }}
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'myGuilds'}") {{ $t('myGuilds') }}
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'guildsDiscovery'}") {{ $t('guildsDiscovery') }}
li.topbar-item(:class="{'active': $route.path.startsWith('/group-plans')}")
li.topbar-item(:class="{'active': $route.path.startsWith('/group-plans'), 'down': $route.path.startsWith('/group-plans') && this.isDesktop()}").droppable
.chevron.rotate(v-if="groupPlans.length > 0", @click='dropdownMobile($event)')
.chevron-icon-down(v-html="icons.chevronDown", v-once)
router-link.nav-link(:to="{name: 'groupPlan'}") {{ $t('group') }}
.topbar-dropdown
router-link.topbar-dropdown-item.dropdown-item(v-for='group in groupPlans', :key='group._id', :to="{name: 'groupPlanDetailTaskInformation', params: {groupId: group._id}}") {{ group.name }}
li.topbar-item(:class="{'active': $route.path.startsWith('/challenges')}")
li.topbar-item(:class="{'active': $route.path.startsWith('/challenges'), 'down': $route.path.startsWith('/challenges') && this.isDesktop()}").droppable
.chevron.rotate(@click='dropdownMobile($event)')
.chevron-icon-down(v-html="icons.chevronDown", v-once)
router-link.nav-link(:to="{name: 'myChallenges'}") {{ $t('challenges') }}
.topbar-dropdown
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'myChallenges'}") {{ $t('myChallenges') }}
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'findChallenges'}") {{ $t('findChallenges') }}
li.topbar-item(:class="{'active': $route.path.startsWith('/help')}")
li.topbar-item(:class="{'active': $route.path.startsWith('/help'), 'down': $route.path.startsWith('/help') && this.isDesktop()}").droppable
.chevron.rotate(@click='dropdownMobile($event)')
.chevron-icon-down(v-html="icons.chevronDown", v-once)
router-link.nav-link(:to="{name: 'faq'}") {{ $t('help') }}
.topbar-dropdown
router-link.topbar-dropdown-item.dropdown-item(:to="{name: 'faq'}") {{ $t('faq') }}
@@ -64,13 +76,13 @@ div
.top-menu-icon.svg-icon(v-html="icons.hourglasses", v-b-tooltip.hover.bottom="$t('mysticHourglassesTooltip')")
span {{ userHourglasses }}
.item-with-icon
.top-menu-icon.svg-icon.gem(v-html="icons.gem", @click='showBuyGemsModal("gems")', v-b-tooltip.hover.bottom="$t('gems')")
a.top-menu-icon.svg-icon.gem(:aria-label="$t('gems')", href="#buy-gems" v-html="icons.gem", @click.prevent='showBuyGemsModal("gems")', v-b-tooltip.hover.bottom="$t('gems')")
span {{userGems}}
.item-with-icon.gold
.top-menu-icon.svg-icon(v-html="icons.gold", v-b-tooltip.hover.bottom="$t('gold')")
.top-menu-icon.svg-icon(:aria-label="$t('gold')", v-html="icons.gold", v-b-tooltip.hover.bottom="$t('gold')")
span {{Math.floor(user.stats.gp * 100) / 100}}
.form-inline.desktop-only
a.item-with-icon(@click="sync", v-b-tooltip.hover.bottom="$t('sync')")
a.item-with-icon(@click="sync", @keyup.enter="sync", role="link", :aria-label="$t('sync')", tabindex="0", v-b-tooltip.hover.bottom="$t('sync')")
.top-menu-icon.svg-icon(v-html="icons.sync")
notification-menu.item-with-icon
user-dropdown.item-with-icon
@@ -81,6 +93,10 @@ div
@import '~client/assets/scss/utils.scss';
@media only screen and (max-width: 1200px) {
.chevron {
display: none
}
.gryphon {
background-image: url('~assets/images/melior@3x.png');
width: 30px;
@@ -96,6 +112,10 @@ div
}
@media only screen and (min-width: 992px) {
.chevron {
display: none
}
.mobile-only {
display: none !important;
}
@@ -111,6 +131,10 @@ div
padding-top: 5px;
height: 56px;
&:hover {
background: $purple-200;
}
&.active:not(:hover) {
box-shadow: 0px -4px 0px $purple-300 inset;
}
@@ -144,12 +168,40 @@ div
order: 1;
text-align: center;
.topbar-dropdown {
transition: max-height 0.25s ease;
}
.topbar-dropdown-item {
background: #432874;
border-bottom: #6133b4 solid 1px;
}
.chevron {
width: 20%;
height: 42px;
position: absolute;
right: 0;
top: 0;
display: block;
}
.chevron-icon-down {
width: 14px;
top: 11px;
right: 12px;
position: absolute;
display: block;
transition: transform 0.25s ease;
}
.down .rotate .chevron-icon-down {
transform: rotate(-180deg);
}
.topbar-item {
position: relative;
&.active {
background: #6133b4;
}
@@ -223,20 +275,25 @@ div
font-weight: bold;
transition: none;
.topbar-dropdown {
display: none; // Display is set to block on hover.
.topbar-dropdown {
overflow: hidden;
max-height: 0;
.topbar-dropdown-item {
line-height: 1.5;
font-size: 16px;
}
}
>a {
padding: .8em 1em !important;
}
&:hover {
&.down {
color: $white !important;
background: $purple-200;
.topbar-dropdown {
display: block; // Open drop-down on hover.
margin-top: 0; // Remove gap between navbar and drop-down.
background: $purple-200;
border-radius: 0px;
@@ -290,6 +347,7 @@ div
margin-right: 24px;
}
&:focus /deep/ .top-menu-icon.svg-icon,
&:hover /deep/ .top-menu-icon.svg-icon {
color: $white;
}
@@ -343,6 +401,7 @@ import gemIcon from 'assets/svg/gem.svg';
import goldIcon from 'assets/svg/gold.svg';
import syncIcon from 'assets/svg/sync.svg';
import svgHourglasses from 'assets/svg/hourglass.svg';
import chevronDownIcon from 'assets/svg/chevron-down.svg';
import logo from 'assets/svg/logo.svg';
import creatorIntro from '../creatorIntro';
@@ -369,12 +428,14 @@ export default {
data () {
return {
isUserDropdownOpen: false,
menuIsOpen: false,
icons: Object.freeze({
gem: gemIcon,
gold: goldIcon,
hourglasses: svgHourglasses,
sync: syncIcon,
logo,
chevronDown: chevronDownIcon,
}),
};
},
@@ -397,6 +458,13 @@ export default {
},
mounted () {
this.getUserGroupPlans();
Array.from(document.getElementById('menu_collapse').getElementsByTagName('a')).forEach(link => {
link.addEventListener('click', this.closeMenu);
});
Array.from(document.getElementsByClassName('topbar-item')).forEach(link => {
link.addEventListener('mouseenter', this.dropdownDesktop);
link.addEventListener('mouseleave', this.dropdownDesktop);
});
},
methods: {
modForm () {
@@ -423,7 +491,40 @@ export default {
this.$root.$emit('bv::show::modal', 'buy-gems', {alreadyTracked: true});
},
dropdownDesktop (hover) {
if (this.isDesktop() && hover.target.classList.contains('droppable')) {
this.dropdown(hover.target);
}
},
dropdownMobile (click) {
this.dropdown(click.currentTarget.parentElement);
},
dropdown (element) {
let droppedElement = document.getElementsByClassName('down')[0];
if (droppedElement && droppedElement !== element) {
droppedElement.classList.remove('down');
droppedElement.lastChild.style.maxHeight = 0;
}
element.classList.toggle('down');
element.lastChild.style.maxHeight = element.classList.contains('down') ? `${element.lastChild.scrollHeight}px` : 0;
},
closeMenu () {
if (this.isMobile()) {
this.menuIsOpen = false;
Array.from(document.getElementsByClassName('droppable')).forEach(droppableElement => {
droppableElement.classList.remove('down');
droppableElement.lastChild.style.maxHeight = 0;
});
}
},
isMobile () {
return document.documentElement.clientWidth < 992;
},
isDesktop () {
return !this.isMobile();
},
},
};
</script>
@@ -0,0 +1,30 @@
<template lang="pug">
base-notification(
:can-remove="canRemove",
:notification="notification",
:read-after-click="true",
@click="action"
)
div(slot="content", v-html="achievementString")
</template>
<script>
import BaseNotification from './base';
export default {
props: ['notification', 'canRemove'],
components: {
BaseNotification,
},
computed: {
achievementString () {
return `<strong>${this.$t('achievement')}</strong>: ${this.$t('achievementJustAddWater')}`;
},
},
methods: {
action () {
this.$root.$emit('bv::show::modal', 'just-add-water');
},
},
};
</script>
@@ -0,0 +1,30 @@
<template lang="pug">
base-notification(
:can-remove="canRemove",
:notification="notification",
:read-after-click="true",
@click="action"
)
div(slot="content", v-html="achievementString")
</template>
<script>
import BaseNotification from './base';
export default {
props: ['notification', 'canRemove'],
components: {
BaseNotification,
},
computed: {
achievementString () {
return `<strong>${this.$t('achievement')}</strong>: ${this.$t('achievementLostMasterclasser')}`;
},
},
methods: {
action () {
this.$root.$emit('bv::show::modal', 'lost-masterclasser');
},
},
};
</script>
@@ -0,0 +1,30 @@
<template lang="pug">
base-notification(
:can-remove="canRemove",
:notification="notification",
:read-after-click="true",
@click="action"
)
div(slot="content", v-html="achievementString")
</template>
<script>
import BaseNotification from './base';
export default {
props: ['notification', 'canRemove'],
components: {
BaseNotification,
},
computed: {
achievementString () {
return `<strong>${this.$t('achievement')}</strong>: ${this.$t('achievementMindOverMatter')}`;
},
},
methods: {
action () {
this.$root.$emit('bv::show::modal', 'mind-over-matter');
},
},
};
</script>

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