Compare commits

..

85 Commits

Author SHA1 Message Date
Kalista Payne
5743fb86b0 5.35.1 2025-04-02 16:45:23 -05:00
Weblate
5443bf2459 Translated using Weblate (Russian)
Currently translated at 89.8% (2977 of 3315 strings)

Translated using Weblate (Ukrainian)

Currently translated at 97.0% (875 of 902 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.7% (900 of 902 strings)

Translated using Weblate (Ukrainian)

Currently translated at 97.0% (875 of 902 strings)

Translated using Weblate (Ukrainian)

Currently translated at 97.0% (875 of 902 strings)

Translated using Weblate (Ukrainian)

Currently translated at 97.0% (875 of 902 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Ukrainian)

Currently translated at 84.8% (224 of 264 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Ukrainian)

Currently translated at 97.4% (419 of 430 strings)

Translated using Weblate (Ukrainian)

Currently translated at 58.4% (1939 of 3315 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 93.9% (3114 of 3315 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 63.6% (156 of 245 strings)

Translated using Weblate (Ukrainian)

Currently translated at 97.0% (875 of 902 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 84.2% (2793 of 3315 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Ukrainian)

Currently translated at 98.3% (239 of 243 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Ukrainian)

Currently translated at 97.0% (875 of 902 strings)

Translated using Weblate (Ukrainian)

Currently translated at 98.4% (256 of 260 strings)

Translated using Weblate (Ukrainian)

Currently translated at 97.0% (875 of 902 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (Ukrainian)

Currently translated at 97.0% (875 of 902 strings)

Translated using Weblate (Indonesian)

Currently translated at 96.8% (874 of 902 strings)

Translated using Weblate (Slovak)

Currently translated at 81.3% (109 of 134 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (Slovak)

Currently translated at 68.2% (577 of 845 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (408 of 408 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Italian)

Currently translated at 11.8% (29 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (German)

Currently translated at 99.9% (3314 of 3315 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (Korean)

Currently translated at 79.0% (713 of 902 strings)

Translated using Weblate (Korean)

Currently translated at 95.4% (232 of 243 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Korean)

Currently translated at 75.4% (681 of 902 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (German)

Currently translated at 99.9% (3313 of 3315 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (German)

Currently translated at 99.9% (3312 of 3315 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.2% (805 of 845 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Polish)

Currently translated at 25.3% (62 of 245 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (German)

Currently translated at 99.7% (3308 of 3315 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Polish)

Currently translated at 24.0% (59 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (408 of 408 strings)

Co-authored-by: Alice Haida <alicehaida.work@gmail.com>
Co-authored-by: Bernardo Oliveira Abrão <bernardooliveiraabrao@gmail.com>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: Emilia Samantha T <emilia.s.thomas@gmail.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Jan Willem Middag <jwmiddag@gmail.com>
Co-authored-by: Jan met de Pet <stijn.koppers@gmail.com>
Co-authored-by: Kim DG <dgkimuniversity@gmail.com>
Co-authored-by: Klaudia Kasprzyk <kasprzyk.klaudiax@gmail.com>
Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Co-authored-by: Pasquale Bosso <protagora87@gmail.com>
Co-authored-by: Raul Ernesto Ceron Lara <raztreuzz1234@gmail.com>
Co-authored-by: Tetiana <merekka13@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Viktor Révész <rviktor@ivankapal.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 沧浪 <963505255@qq.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/id/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/character/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/content/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/content/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/death/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/front/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/uk/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Death
Translation: Habitica/Defaulttasks
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Loginincentives
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Pets
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2025-04-02 21:00:16 +02:00
Phillip Thelen
c0d5566417 Make sure no userhistory exists before initializing it (#15415) 2025-03-26 13:37:52 -05:00
negue
ded71b46c5 use the correct like icon/boldness when not the current user liked it (#15409) 2025-03-25 12:12:44 -05:00
Kalista Payne
9693ad321c 5.35.0 2025-03-24 15:40:18 -05:00
Weblate
dd3679f329 Translated using Weblate (Hungarian)
Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (French)

Currently translated at 100.0% (408 of 408 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3315 of 3315 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Italian)

Currently translated at 11.4% (28 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (408 of 408 strings)

Translated using Weblate (German)

Currently translated at 100.0% (408 of 408 strings)

Translated using Weblate (Slovak)

Currently translated at 75.3% (101 of 134 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3315 of 3315 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (408 of 408 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (408 of 408 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (902 of 902 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3315 of 3315 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.7% (429 of 430 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 93.4% (3097 of 3315 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Polish)

Currently translated at 23.2% (57 of 245 strings)

Translated using Weblate (Italian)

Currently translated at 11.0% (27 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Spanish)

Currently translated at 99.5% (841 of 845 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.5% (395 of 405 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (902 of 902 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (902 of 902 strings)

Translated using Weblate (German)

Currently translated at 100.0% (902 of 902 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3315 of 3315 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (264 of 264 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.8% (3310 of 3315 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (405 of 405 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (902 of 902 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.2% (262 of 264 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Polish)

Currently translated at 22.4% (55 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Spanish)

Currently translated at 99.1% (838 of 845 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3315 of 3315 strings)

Translated using Weblate (French)

Currently translated at 100.0% (264 of 264 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (264 of 264 strings)

Translated using Weblate (German)

Currently translated at 100.0% (264 of 264 strings)

Translated using Weblate (French)

Currently translated at 99.9% (3314 of 3315 strings)

Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 strings)

Translated using Weblate (Spanish)

Currently translated at 99.0% (837 of 845 strings)

Translated using Weblate (French)

Currently translated at 100.0% (405 of 405 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (405 of 405 strings)

Translated using Weblate (French)

Currently translated at 100.0% (902 of 902 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (260 of 260 strings)

Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Klaudia Kasprzyk <kasprzyk.klaudiax@gmail.com>
Co-authored-by: Larissa dos Santos Brasil <larissabrasil009@gmail.com>
Co-authored-by: Pasquale Bosso <protagora87@gmail.com>
Co-authored-by: Rivian <rebecca.jason.books@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Viktor Révész <rviktor@ivankapal.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/character/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/
Translate-URL: https://translate.habitica.com/projects/habitica/content/es/
Translate-URL: https://translate.habitica.com/projects/habitica/content/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/content/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/content/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/es/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/hu/
Translation: Habitica/Backgrounds
Translation: Habitica/Character
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Npc
Translation: Habitica/Pets
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Subscriber
2025-03-24 21:33:59 +01:00
Kalista Payne
f3029953dc Squashed commit of the following:
commit bde71cc45d
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Mar 20 15:25:11 2025 -0500

    fix(potions): add missing Cryptid release date

commit 1a50413402
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Tue Mar 18 16:52:19 2025 -0500

    fix(wacky): revise text, address linting

commit 2d49a16f55
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Mar 18 16:05:17 2025 +0100

    Fix displaying countdown for recurring event items

commit cf20b30975
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Fri Mar 14 18:01:37 2025 -0500

    fix(lint): line length

commit e39e490e41
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Fri Mar 14 17:55:12 2025 -0500

    fix(foolin): correct animation and end date of potions

commit 164e2eefdd
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Mar 13 17:25:14 2025 -0500

    fix(event): Panda Cub typo, shift start date

commit 394c922287
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Mar 13 17:02:12 2025 -0500

    fix(test): account for addition of Cryptid

commit bea9e40338
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Thu Mar 13 16:52:37 2025 -0500

    feat(event): April Fools 2025
2025-03-21 15:02:31 -05:00
negue
01881b2fd8 Avatar Component: fix pet position (#15414) 2025-03-21 14:55:32 -05:00
Natalie
11a22d0f5d April 2025 content build (#15411)
* chore: April 2025 CSS

* chore: April 2025 subscriber and armoire items, backgrounds

* chore: Update April 2025 subscriber items

* chore: April 2025 CSS fix

* chore: April 2025 pet quest and magic hatching potion

* fix: April 2025 fixes

* fix: typo

* fix: typo

* fix(typo): whitespace

---------

Co-authored-by: Kalista Payne <sabrecat@gmail.com>
2025-03-20 14:30:51 -05:00
Kalista Payne
5f9bf07045 5.34.4 2025-03-20 11:46:03 -05:00
Weblate
719c03e2f5 Translated using Weblate (German)
Currently translated at 99.9% (3302 of 3303 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Dutch)

Currently translated at 78.7% (2600 of 3303 strings)

Translated using Weblate (Dutch)

Currently translated at 86.4% (210 of 243 strings)

Translated using Weblate (Dutch)

Currently translated at 95.0% (173 of 182 strings)

Translated using Weblate (Dutch)

Currently translated at 25.7% (63 of 245 strings)

Translated using Weblate (Dutch)

Currently translated at 60.4% (55 of 91 strings)

Translated using Weblate (Dutch)

Currently translated at 67.6% (176 of 260 strings)

Translated using Weblate (Dutch)

Currently translated at 67.6% (176 of 260 strings)

Translated using Weblate (Dutch)

Currently translated at 76.0% (200 of 263 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Dutch)

Currently translated at 94.5% (172 of 182 strings)

Translated using Weblate (Dutch)

Currently translated at 24.4% (60 of 245 strings)

Translated using Weblate (Dutch)

Currently translated at 57.1% (52 of 91 strings)

Translated using Weblate (Dutch)

Currently translated at 91.4% (43 of 47 strings)

Translated using Weblate (Dutch)

Currently translated at 91.4% (43 of 47 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Dutch)

Currently translated at 56.0% (51 of 91 strings)

Translated using Weblate (Dutch)

Currently translated at 87.5% (7 of 8 strings)

Translated using Weblate (Dutch)

Currently translated at 78.6% (2599 of 3303 strings)

Translated using Weblate (Dutch)

Currently translated at 23.6% (58 of 245 strings)

Translated using Weblate (Dutch)

Currently translated at 23.6% (58 of 245 strings)

Translated using Weblate (Dutch)

Currently translated at 56.0% (51 of 91 strings)

Translated using Weblate (Dutch)

Currently translated at 23.2% (57 of 245 strings)

Translated using Weblate (Dutch)

Currently translated at 23.2% (57 of 245 strings)

Translated using Weblate (Dutch)

Currently translated at 54.9% (50 of 91 strings)

Translated using Weblate (Dutch)

Currently translated at 66.9% (174 of 260 strings)

Translated using Weblate (Dutch)

Currently translated at 72.5% (312 of 430 strings)

Translated using Weblate (Dutch)

Currently translated at 22.4% (55 of 245 strings)

Translated using Weblate (Dutch)

Currently translated at 84.5% (707 of 836 strings)

Translated using Weblate (Dutch)

Currently translated at 66.5% (173 of 260 strings)

Translated using Weblate (Japanese)

Currently translated at 99.8% (898 of 899 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Japanese)

Currently translated at 97.8% (46 of 47 strings)

Translated using Weblate (Romanian)

Currently translated at 4.4% (11 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (German)

Currently translated at 99.9% (3300 of 3303 strings)

Translated using Weblate (French)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (French)

Currently translated at 100.0% (260 of 260 strings)

Co-authored-by: Alison Alex <spamkari@hotmail.com>
Co-authored-by: Anna Wolthuis <annawolthuis20@gmail.com>
Co-authored-by: Avril Manoah <manoah.avril@gmail.com>
Co-authored-by: Gabone <gabyjoaca2@gmail.com>
Co-authored-by: Julius Eikmans <jcs.e@icloud.com>
Co-authored-by: M Timmermans <merel11timmermans@gmail.com>
Co-authored-by: Mika <isekai.chr@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Viktor Révész <rviktor@ivankapal.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/character/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/content/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ro/
Translate-URL: https://translate.habitica.com/projects/habitica/front/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/nl/
Translation: Habitica/Backgrounds
Translation: Habitica/Character
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Loginincentives
Translation: Habitica/Overview
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Subscriber
2025-03-20 15:11:05 +01:00
Phillip Thelen
379afa9554 Improve Adminpanel with local logs (#15404)
* log armoire, quoest response and cron events to history

* show user history in admin panel

* allow stats to be edited from admin panel

* Improve admin panel stats input

* improve setting client in history

* fix tests

* fix lint

* fix armoire buying issue

* Improve hero saving

* Formatting fix

* Improve user history logging

* allow class to be changed from admin panel

* make terminating subscriptions easier

* support decimal extraMonths

* Fix editing some achievements in admin panel

* log if a user invites party to quest

* Log more quest events into user history

* make userhistory length configurable

* fix some numbered achievements

* fix extraMonths field

* Automatically set up group plan subs with admin panel

* show party info nicer in admin panel

* improve admin panel sub handling

* add missing brace

* display when there are unsaved changes

* fix setting group plan

* fix showing group id

* Display group plan info in admin panel

* fix setting hourglass promo date

* Improve termination handling in admin panel

* reload data after certain save events in admin panel

* remove console

* fix plan.extraMonths not being reset if terminating a sub

* add more options when cancelling subs

* reload data after group plan change

* Add a way to remove users from a party

* fix issue with removing user from party

* pass party id correctly

* correctly call async function

* Improve sub display in admin panel

* fix line length

* fix line

* shorter

* plaid

* fix(lint): vue code style

---------

Co-authored-by: Kalista Payne <sabrecat@gmail.com>
2025-03-17 16:48:21 -05:00
Kalista Payne
dbc23e89b8 chore(git): update subproj 2025-03-17 16:25:06 -05:00
negue
0c6e254742 correct async/await usages, improve email tests (#15408) 2025-03-17 16:11:38 -05:00
Kalista Payne
8327e69bdd 5.34.3 2025-03-17 09:00:49 -05:00
Kalista Payne
2d953f4f59 chore(git): update subproject 2025-03-17 09:00:44 -05:00
Weblate
7118d63949 Merge branch 'origin/develop' into Weblate. 2025-03-17 14:58:58 +01:00
Weblate
20af8d038e Translated using Weblate (German)
Currently translated at 99.8% (3298 of 3303 strings)

Translated using Weblate (Portuguese)

Currently translated at 72.2% (604 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Slovak)

Currently translated at 73.1% (98 of 134 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (German)

Currently translated at 99.7% (3296 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 89.7% (236 of 263 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.9% (273 of 276 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 97.7% (3191 of 3265 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 68.9% (2278 of 3303 strings)

Translated using Weblate (German)

Currently translated at 99.7% (3294 of 3303 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 87.0% (229 of 263 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.6% (259 of 260 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (262 of 262 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (German)

Currently translated at 99.6% (3292 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (German)

Currently translated at 99.6% (3290 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (899 of 899 strings)

Co-authored-by: AlexFad <2077505931@qq.com>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Samir Mahmoud Samir <moonlordslayer080@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Viktor Révész <rviktor@ivankapal.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 沧浪 <963505255@qq.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/character/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/content/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Content
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Inventory
Translation: Habitica/Limited
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Subscriber
2025-03-17 14:58:47 +01:00
Phillip Thelen
3d9dfbb5e1 fix jade potion release date (#15413) 2025-03-17 08:55:47 -05:00
Natalie
ae0b966f45 fix: update currency display in setting for change class (#15391)
* fix: add neededCurrencyOnly prop

* fix: worked on logic for displaying only gems

* fix: removed gold & hourglasses if neededCurrencyOnly is true

* fix: aligned gem count properly

* fix: updates based on some comments (all comments answered)

* fix: working on displaying only required currency in a new array

* chore: trying to figure out currency logic

* trying to use .find() now

* remove unneeded line of code

* still working on finding/filtering the currency

* trying to move requiredCurrency into a new function (?!)

* fix: added logic to filter for a single currency and fixed CSS

* fix: clean up code

* fix: really clean up code, sheesh

* fix: updated per comments on PR

* fix(style): vertically align elements in your-balance

---------

Co-authored-by: Kalista Payne <sabrecat@gmail.com>
2025-03-13 15:01:50 -05:00
Kalista Payne
cef8a34c06 5.34.2 2025-03-12 09:01:14 -05:00
Weblate
6432823eec Translated using Weblate (Hungarian)
Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 17.1% (42 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (German)

Currently translated at 99.5% (3288 of 3303 strings)

Co-authored-by: Eddy Nottingham <habitica.com.scone566@simplelogin.com>
Co-authored-by: Sujay Thomas <mail.sujaythomas@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Viktor Révész <rviktor@ivankapal.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/character/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/character/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/content/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hu/
Translation: Habitica/Backgrounds
Translation: Habitica/Character
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Questscontent
2025-03-12 09:49:58 +01:00
Phillip Thelen
563b780d85 fix system messages not being translated (#15405) 2025-03-11 16:58:39 -05:00
Phillip Thelen
aa9b1b2cac More Sprites (#15400)
* remove need for quest_ notif_ and inventory_quest_ in css

* fix test
2025-03-11 16:48:19 -05:00
Kalista Payne
401e541b86 5.34.1 2025-03-11 09:56:21 -05:00
Weblate
c13bed3bad Translated using Weblate (Hungarian)
Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (German)

Currently translated at 99.4% (3286 of 3303 strings)

Translated using Weblate (Indonesian)

Currently translated at 95.6% (109 of 114 strings)

Translated using Weblate (Indonesian)

Currently translated at 98.2% (57 of 58 strings)

Translated using Weblate (Indonesian)

Currently translated at 88.1% (379 of 430 strings)

Translated using Weblate (Indonesian)

Currently translated at 73.3% (2424 of 3303 strings)

Translated using Weblate (Indonesian)

Currently translated at 89.3% (217 of 243 strings)

Translated using Weblate (Indonesian)

Currently translated at 74.2% (182 of 245 strings)

Translated using Weblate (Indonesian)

Currently translated at 83.5% (76 of 91 strings)

Translated using Weblate (Indonesian)

Currently translated at 89.0% (98 of 110 strings)

Translated using Weblate (Indonesian)

Currently translated at 96.3% (866 of 899 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Turkish)

Currently translated at 31.0% (76 of 245 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Turkish)

Currently translated at 82.3% (214 of 260 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Italian)

Currently translated at 10.6% (26 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (German)

Currently translated at 99.4% (3284 of 3303 strings)

Translated using Weblate (Italian)

Currently translated at 10.2% (25 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Japanese)

Currently translated at 91.5% (765 of 836 strings)

Translated using Weblate (German)

Currently translated at 99.5% (242 of 243 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 97.4% (419 of 430 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.7% (240 of 243 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (German)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (260 of 260 strings)

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Hungarian)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (French)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (French)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (262 of 262 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (47 of 47 strings)

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Hungarian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (German)

Currently translated at 99.3% (3280 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Russian)

Currently translated at 91.3% (764 of 836 strings)

Translated using Weblate (Korean)

Currently translated at 98.8% (165 of 167 strings)

Translated using Weblate (Korean)

Currently translated at 54.1% (1788 of 3303 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Hebrew)

Currently translated at 55.7% (241 of 432 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (German)

Currently translated at 99.2% (3278 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hebrew)

Currently translated at 66.7% (558 of 836 strings)

Translated using Weblate (Hebrew)

Currently translated at 87.2% (96 of 110 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (263 of 263 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (432 of 432 strings)

Translated using Weblate (Hebrew)

Currently translated at 88.7% (213 of 240 strings)

Translated using Weblate (Hebrew)

Currently translated at 83.7% (201 of 240 strings)

Translated using Weblate (Italian)

Currently translated at 9.7% (24 of 245 strings)

Translated using Weblate (German)

Currently translated at 99.1% (3276 of 3303 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Italian)

Currently translated at 9.3% (23 of 245 strings)

Translated using Weblate (German)

Currently translated at 99.1% (3274 of 3303 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.2% (258 of 260 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 97.6% (3187 of 3265 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 91.3% (764 of 836 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 84.7% (223 of 263 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 93.5% (3089 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (262 of 262 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (262 of 262 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (German)

Currently translated at 99.0% (3272 of 3303 strings)

Translated using Weblate (German)

Currently translated at 99.0% (3271 of 3303 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.5% (399 of 401 strings)

Translated using Weblate (Italian)

Currently translated at 8.9% (22 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Hebrew)

Currently translated at 24.2% (67 of 276 strings)

Translated using Weblate (Hebrew)

Currently translated at 80.0% (192 of 240 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 15.5% (38 of 245 strings)

Translated using Weblate (German)

Currently translated at 99.5% (244 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 93.4% (3087 of 3303 strings)

Translated using Weblate (German)

Currently translated at 99.6% (833 of 836 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (432 of 432 strings)

Translated using Weblate (Bulgarian)

Currently translated at 98.4% (190 of 193 strings)

Translated using Weblate (German)

Currently translated at 98.9% (3268 of 3303 strings)

Translated using Weblate (Hebrew)

Currently translated at 39.3% (1301 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hebrew)

Currently translated at 39.3% (1301 of 3303 strings)

Translated using Weblate (Hebrew)

Currently translated at 83.3% (45 of 54 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 94.5% (379 of 401 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Hebrew)

Currently translated at 78.9% (90 of 114 strings)

Translated using Weblate (Hebrew)

Currently translated at 62.5% (5 of 8 strings)

Translated using Weblate (Hebrew)

Currently translated at 55.7% (241 of 432 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Hebrew)

Currently translated at 50.3% (131 of 260 strings)

Translated using Weblate (German)

Currently translated at 99.4% (831 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (262 of 262 strings)

Translated using Weblate (French)

Currently translated at 100.0% (263 of 263 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Hebrew)

Currently translated at 61.4% (70 of 114 strings)

Translated using Weblate (Hebrew)

Currently translated at 50.0% (4 of 8 strings)

Translated using Weblate (French)

Currently translated at 100.0% (276 of 276 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Hebrew)

Currently translated at 1.2% (3 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.7% (400 of 401 strings)

Translated using Weblate (French)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (110 of 110 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Hebrew)

Currently translated at 50.3% (131 of 260 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (262 of 262 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (263 of 263 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (276 of 276 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (432 of 432 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (German)

Currently translated at 100.0% (263 of 263 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (German)

Currently translated at 100.0% (276 of 276 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (262 of 262 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 99.1% (113 of 114 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (432 of 432 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Hungarian)

Currently translated at 96.4% (3185 of 3303 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Hungarian)

Currently translated at 63.7% (2106 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (German)

Currently translated at 100.0% (401 of 401 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Portuguese)

Currently translated at 97.0% (130 of 134 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 95.1% (233 of 245 strings)

Translated using Weblate (Portuguese)

Currently translated at 94.5% (86 of 91 strings)

Translated using Weblate (Portuguese)

Currently translated at 98.7% (392 of 397 strings)

Translated using Weblate (Portuguese)

Currently translated at 98.9% (191 of 193 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.0% (109 of 110 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 93.3% (3084 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 63.4% (2096 of 3303 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 93.1% (3078 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 63.2% (2089 of 3303 strings)

Translated using Weblate (German)

Currently translated at 98.8% (3266 of 3303 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 92.6% (227 of 245 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 92.6% (227 of 245 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 62.6% (2068 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Co-authored-by: Annika Nuding <annikanuding@gmail.com>
Co-authored-by: Chen Shaham <chenshaham@gmail.com>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: Deleted User <noreply+1293@weblate.org>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Filip Kober <koberfilip2@gmail.com>
Co-authored-by: ForbiddenFigs <sorautai@outlook.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Kacchan <h.mrena97@gmail.com>
Co-authored-by: Kovács Máté <kovacsur10@gmail.com>
Co-authored-by: Miya <baddybadges@gmail.com>
Co-authored-by: Muhammad Naufal Ramadhan <naufalramadhan281004@gmail.com>
Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Co-authored-by: Nell Chant <doubletailor@gmail.com>
Co-authored-by: Pasquale Bosso <protagora87@gmail.com>
Co-authored-by: Siying Li <lsy68653654@qq.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Sujay Thomas <mail.sujaythomas@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Viktor Révész <rviktor@ivankapal.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Wyatt <1697570085@qq.com>
Co-authored-by: XiaoWan <2558968618@qq.com>
Co-authored-by: Yaşar Efe Çelik <yasar.123.sevda@gmail.com>
Co-authored-by: 沧浪 <963505255@qq.com>
Co-authored-by: 海岛钓鱼佬 <963505255@qq.com>
Co-authored-by: 박제균 <parkjekyun@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/he/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/id/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/he/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/id/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/character/bg/
Translate-URL: https://translate.habitica.com/projects/habitica/character/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/character/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/character/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/character/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/id/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/
Translate-URL: https://translate.habitica.com/projects/habitica/content/es/
Translate-URL: https://translate.habitica.com/projects/habitica/content/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/content/he/
Translate-URL: https://translate.habitica.com/projects/habitica/content/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/content/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/content/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/content/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/death/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/he/
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/he/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/id/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/front/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/he/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/id/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/de/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/es/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/he/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/id/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/de/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/es/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/he/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/id/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/de/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/es/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/he/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/id/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/he/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/he/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/id/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/he/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/es/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/he/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/he/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/spells/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/hu/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Death
Translation: Habitica/Defaulttasks
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Inventory
Translation: Habitica/Limited
Translation: Habitica/Loginincentives
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Spells
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2025-03-11 15:54:34 +01:00
negue
b3c4817fb4 Private Messages: improve "scrollToBottom" logic 2025-03-05 22:18:38 +01:00
negue
7c9c45ac5f combined messages restyling - next round (#15386)
* split component prepare new views / states

* extract empty and disabled state as components

* fix empty state mail icon

* first logic switching between modes, move page to /private-messages/index.vue

* extract autoCompleteHelper.js

* style header + start new message input

* style plus button + focus input

* state logic, types for sanity

* WIP PM new Message started

* add /members/username test

* first design changes to messageCard

* delete private message or chat - based on the mode

* copy as todo

* mention links to modal

* report chat or private message

* WIP likeButton

* likeButton styling

* hide like on private message cards

* fix unit test

* replace copy as todo - to just a copy to clipboard

* style changes

* menu position + like button width

* dropdown items background + like font

* fix like button padding

* move api endpoints and tests around to group inbox methods  + like for inbox private messages

* restyle system messages

* Dropdown Radius and Padding

* WIP system messages

* fix lint

* copy delta commit of allowing liking own private messages

* enable liking private messages

* fix menu non hovered item icon color

* fix import path

* ignore background on system messages

* requested changes + migration

* update migration to update the unique id to some messages and delete the duplicates

* migration based on users pagination

* fix(migration): use Promise.all

* change to bulkWrites per User, and all messages in one run (of a user)

* check for array

* use rest operator ...

* skip sorting to get the users

* remove migration, disable like for private messages without uniqueMessageId

* lean+bulkWrite for likes, add time checks for like and auth for further debugging

* add a limit 2 get the messages by uniqueId

* Adding a simple server start script

* remove pinned nodemon dep

* fix inbox controller/tests

* fix / requested style changes

* fix empty state padding /

* hide avatar weapons on messages - fix avatar spacing on messages

* Hourglass Simplification (#15323)

* begin removing obsolete tests

* begin refactoring

* update cron tests

* cleanup

* finish basic implementation of new logic

* add more subscription tests

* subscription test improvements

* return nextHourglassDate again

* fix gem limit

* fix(test): short circuit this.

* fix(admin): correct logic and style for shrimple subs

* WIP(frontend): draft of main subs page view

* fix hourglass count

* Fix hourglass logic for upgrades

* fix admin panel display

* WIP(subs): extant Stripe state

* fix admin panel strings

* fix missing transaction type

* add new field for cumulative subscription count

* show date for hourglass bonus if it was received

* fix test

* feat(subscription): max Gems progress readout

* fix(css): correct and refactor heights and selection states

* fix(subs): correct border-radius and redirect

* fix(stripe): correct redirect after success

* Admin panel display fixes

* don’t give additional HG for new sub if they already got one this month

* fix issue with promo hourglasses

* fix(subscription): update layout when gifting

* fix(subscriptions): more gift layout revisions

* fix(subscriptions): minor visual updates

* fix(subs): pass autoRenews through Stripe

* fix(subs): gifts DON't renew

* fix(lint): unnecessary ternary

* fix(lint): do negate object ig

* fix(subs): try again on gifts

* fix(subs): unhovery and un-12-monthy

* fix bug with incorrectly giving HG bonus

* remove only

* fix test

* fix test

* fix(subs): also redirect to subs after gift sub

* fix(subs): fix typeError

* fix(g1g1): don't try to find Gems promo during bogo

---------

Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: Kalista Payne <sabe@habitica.com>

* chore(sprites): update subproject

* fix(layout): tighten cancellation note

* fix(subs): Google wording and HG escape

* chore(testing): fake g1g1 dates

* fix(subs): don't hide HG preview entirely

* fix(subs): center next hourglass message

* working validatedTextInput.vue within start-new-conversation-input-header.vue 🎉

* fix(git): remove changes from old develop

* Revert "fix(git): remove changes from old develop"

This reverts commit 0e30f7df00.

* fix(git): no actually just this file i guesss

* adding an empty loading state, hiding

* fought the avatar arch nemesis again

* fix chatMessages (party chat) message spacing

* move disabled text back to above the input area - re-enable input area

* show disabled private messages top panel

* fix font color

* fixing uiStates - removing disabled - moving the own user check to the last

* fix(lint): add missing prop defaults

* fix(lint): object default should be fn

* fix(chat): correct grammar in error

* remove weapon position relative

* revert most of avatar.vue changes, add back weapons in chat message UI

* show date tooltip above system / skill messages

* fix toggle disable icon position

* trivial CSS cleanup

* fix(typo): English syntax in test

* chore(test): small style cleanup

* chore(logging): revert debug function

* chore(debug): remove timers from inbox like

---------

Co-authored-by: SabreCat <sabe@habitica.com>
Co-authored-by: Kalista Payne <sabrecat@gmail.com>
Co-authored-by: Phillip Thelen <phillip@habitica.com>
2025-03-04 17:00:24 -06:00
Phillip Thelen
95142e3684 Update test.yml (#15397) 2025-03-04 16:19:44 -06:00
Kalista Payne
dc1cce6ddb 5.34.0 2025-02-20 15:29:15 -06:00
Weblate
43cf77f33c Translated using Weblate (Spanish)
Currently translated at 99.5% (3288 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Spanish)

Currently translated at 99.5% (832 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (German)

Currently translated at 98.8% (826 of 836 strings)

Translated using Weblate (Russian)

Currently translated at 40.8% (100 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (French)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Russian)

Currently translated at 40.4% (99 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 40.0% (98 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 40.0% (98 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 39.1% (96 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 91.2% (219 of 240 strings)

Translated using Weblate (Russian)

Currently translated at 38.7% (95 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 38.7% (95 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 38.7% (95 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 37.5% (92 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 37.1% (91 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 36.7% (90 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Russian)

Currently translated at 90.8% (218 of 240 strings)

Translated using Weblate (Russian)

Currently translated at 90.8% (218 of 240 strings)

Translated using Weblate (Russian)

Currently translated at 36.3% (89 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 36.3% (89 of 245 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Russian)

Currently translated at 99.3% (893 of 899 strings)

Translated using Weblate (Russian)

Currently translated at 99.2% (892 of 899 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Spanish)

Currently translated at 99.4% (831 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 62.6% (2068 of 3303 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.9% (804 of 821 strings)

Translated using Weblate (Portuguese)

Currently translated at 72.0% (602 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Spanish)

Currently translated at 99.1% (829 of 836 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.4% (885 of 899 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.6% (896 of 899 strings)

Translated using Weblate (Hungarian)

Currently translated at 57.9% (1915 of 3303 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.4% (800 of 821 strings)

Translated using Weblate (Hungarian)

Currently translated at 57.6% (1903 of 3303 strings)

Translated using Weblate (Hungarian)

Currently translated at 57.5% (1900 of 3303 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.0% (797 of 821 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (836 of 836 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Bulgarian)

Currently translated at 84.0% (79 of 94 strings)

Translated using Weblate (Bulgarian)

Currently translated at 84.0% (79 of 94 strings)

Translated using Weblate (Spanish)

Currently translated at 98.4% (823 of 836 strings)

Translated using Weblate (Spanish)

Currently translated at 98.7% (3263 of 3303 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.8% (795 of 821 strings)

Translated using Weblate (Spanish)

Currently translated at 98.3% (822 of 836 strings)

Translated using Weblate (Spanish)

Currently translated at 98.5% (3256 of 3303 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.8% (795 of 821 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.8% (795 of 821 strings)

Translated using Weblate (French)

Currently translated at 98.4% (823 of 836 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3303 of 3303 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.5% (793 of 821 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (French)

Currently translated at 99.8% (3297 of 3303 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.4% (792 of 821 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (French)

Currently translated at 99.3% (3280 of 3303 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.7% (897 of 899 strings)

Translated using Weblate (French)

Currently translated at 99.1% (3275 of 3303 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.7% (897 of 899 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.7% (897 of 899 strings)

Translated using Weblate (German)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.7% (897 of 899 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 97.6% (3187 of 3265 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.7% (897 of 899 strings)

Translated using Weblate (French)

Currently translated at 100.0% (899 of 899 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (262 of 262 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Hungarian)

Currently translated at 58.1% (1898 of 3265 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 62.8% (154 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.4% (792 of 821 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.2% (378 of 397 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (397 of 397 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (896 of 896 strings)

Co-authored-by: Anna <shiloanna007@gmail.com>
Co-authored-by: Besogon <victoria_murka@mail.ru>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: ForbiddenFigs <sorautai@outlook.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Nell Chant <doubletailor@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Viktor Révész <rviktor@ivankapal.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: razil <boss.razmarin@gmail.com>
Co-authored-by: 小王 <963505255@qq.com>
Co-authored-by: 海岛钓鱼佬 <963505255@qq.com>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/content/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/death/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/bg/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/es/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/hu/
Translation: Habitica/Backgrounds
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Death
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Limited
Translation: Habitica/Messages
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Subscriber
2025-02-20 22:28:02 +01:00
Natalie
93780d7056 March 2025 Content Build (#15392)
* build: March 2025 css, backgrounds, subscriber gear, armoire

* build: March 2025 quests, seasonal gear, various fixes

* fix: fix string

* fix: fixes to string errors

* fix: string fixes
2025-02-18 15:48:08 -06:00
Kalista Payne
2ad17d408e 5.33.3 2025-02-18 15:05:44 -06:00
Weblate
b0f7567367 Translated using Weblate (Japanese)
Currently translated at 93.0% (764 of 821 strings)

Translated using Weblate (Hungarian)

Currently translated at 54.8% (1790 of 3265 strings)

Translated using Weblate (Hungarian)

Currently translated at 53.5% (1748 of 3265 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (262 of 262 strings)

Translated using Weblate (Hungarian)

Currently translated at 52.1% (1704 of 3265 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Hungarian)

Currently translated at 59.3% (532 of 896 strings)

Translated using Weblate (Hungarian)

Currently translated at 79.3% (208 of 262 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (432 of 432 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 77.4% (2528 of 3265 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 93.0% (764 of 821 strings)

Translated using Weblate (French)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (French)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Hungarian)

Currently translated at 94.8% (258 of 272 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.2% (378 of 397 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Hungarian)

Currently translated at 82.8% (203 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (821 of 821 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 94.9% (377 of 397 strings)

Translated using Weblate (Hungarian)

Currently translated at 52.1% (1704 of 3265 strings)

Translated using Weblate (Hungarian)

Currently translated at 49.7% (122 of 245 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.1% (789 of 821 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (821 of 821 strings)

Translated using Weblate (Hungarian)

Currently translated at 48.5% (119 of 245 strings)

Translated using Weblate (Hungarian)

Currently translated at 26.1% (64 of 245 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Hungarian)

Currently translated at 8.9% (22 of 245 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (Hungarian)

Currently translated at 96.2% (790 of 821 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (432 of 432 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 95.4% (784 of 821 strings)

Translated using Weblate (Hungarian)

Currently translated at 91.5% (752 of 821 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (397 of 397 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (397 of 397 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (193 of 193 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (German)

Currently translated at 99.2% (133 of 134 strings)

Translated using Weblate (German)

Currently translated at 99.2% (133 of 134 strings)

Translated using Weblate (Czech)

Currently translated at 95.2% (159 of 167 strings)

Translated using Weblate (Russian)

Currently translated at 91.2% (2978 of 3265 strings)

Translated using Weblate (Russian)

Currently translated at 99.3% (890 of 896 strings)

Translated using Weblate (German)

Currently translated at 100.0% (3265 of 3265 strings)

Translated using Weblate (German)

Currently translated at 100.0% (3265 of 3265 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (French)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (French)

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Korean)

Currently translated at 98.5% (132 of 134 strings)

Translated using Weblate (Korean)

Currently translated at 6.9% (17 of 245 strings)

Translated using Weblate (Korean)

Currently translated at 71.9% (645 of 896 strings)

Translated using Weblate (Korean)

Currently translated at 49.2% (129 of 262 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Korean)

Currently translated at 81.9% (77 of 94 strings)

Translated using Weblate (Korean)

Currently translated at 91.6% (153 of 167 strings)

Translated using Weblate (Korean)

Currently translated at 67.3% (291 of 432 strings)

Translated using Weblate (Korean)

Currently translated at 79.5% (191 of 240 strings)

Translated using Weblate (Korean)

Currently translated at 54.6% (1785 of 3265 strings)

Translated using Weblate (Korean)

Currently translated at 88.8% (48 of 54 strings)

Translated using Weblate (Korean)

Currently translated at 89.3% (42 of 47 strings)

Translated using Weblate (Korean)

Currently translated at 93.9% (373 of 397 strings)

Translated using Weblate (Korean)

Currently translated at 54.9% (50 of 91 strings)

Translated using Weblate (German)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (German)

Currently translated at 100.0% (182 of 182 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 73.0% (179 of 245 strings)

Translated using Weblate (French)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (German)

Currently translated at 99.1% (243 of 245 strings)

Translated using Weblate (French)

Currently translated at 99.5% (244 of 245 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 62.0% (152 of 245 strings)

Translated using Weblate (Indonesian)

Currently translated at 73.4% (180 of 245 strings)

Translated using Weblate (Indonesian)

Currently translated at 96.0% (861 of 896 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 62.0% (152 of 245 strings)

Translated using Weblate (German)

Currently translated at 98.7% (242 of 245 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (245 of 245 strings)

Translated using Weblate (Spanish)

Currently translated at 99.5% (244 of 245 strings)

Translated using Weblate (Portuguese)

Currently translated at 33.7% (82 of 243 strings)

Translated using Weblate (Portuguese)

Currently translated at 73.3% (602 of 821 strings)

Translated using Weblate (Portuguese)

Currently translated at 56.0% (51 of 91 strings)

Translated using Weblate (German)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (German)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Portuguese)

Currently translated at 97.2% (107 of 110 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (896 of 896 strings)

Co-authored-by: César Orlando Pallares Delgado <copdeb@gmail.com>
Co-authored-by: Céu <marcel.ufscar@gmail.com>
Co-authored-by: Diego Benitez <diego.benitez@bigpond.com>
Co-authored-by: Finrod <963505255@qq.com>
Co-authored-by: ForbiddenFigs <sorautai@outlook.com>
Co-authored-by: Hexe des Windes (she/her) <krausanna1@gmail.com>
Co-authored-by: Icaro <icaro.mascarenhas@outlook.com>
Co-authored-by: Ikmal <ikmal.s.16@gmail.com>
Co-authored-by: Jackal <qwerty70244@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Katharina <katharinaanna.wilding@gmail.com>
Co-authored-by: Leslie Munguía <moongeeuh@gmail.com>
Co-authored-by: Lio Zam <zerofux@web.de>
Co-authored-by: Marius <mariusschmid11@gmail.com>
Co-authored-by: Miya <baddybadges@gmail.com>
Co-authored-by: Natalie Luhrs <eilatan@gmail.com>
Co-authored-by: Raul Ernesto Ceron Lara <raztreuzz1234@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Viktor Révész <rviktor@ivankapal.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 小王 <963505255@qq.com>
Co-authored-by: 이채린 <cofls1256@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/cs/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/de/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/id/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/character/es/
Translate-URL: https://translate.habitica.com/projects/habitica/character/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/character/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/content/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/content/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/content/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/es/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/death/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/id/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/front/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/noscript/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/es/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/es/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/spells/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/hu/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ko/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Death
Translation: Habitica/Defaulttasks
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Loginincentives
Translation: Habitica/Messages
Translation: Habitica/Noscript
Translation: Habitica/Npc
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Spells
Translation: Habitica/Subscriber
2025-02-18 22:03:17 +01:00
Phillip Thelen
3f2b1d3f79 Update .eslintrc.js (#15388)
Add `require-await` to eslint config
2025-02-11 12:05:28 -06:00
Phillip Thelen
29eb8ca10b log slow requests to loggly (#15364) 2025-02-11 12:05:06 -06:00
Phillip Thelen
8c71ca12b8 Support sprite version of armoire icon (#15354)
* Use sprite component for armoire sprite

* use gif version of armoire sprite

* fix(import): sprite component path

---------

Co-authored-by: Kalista Payne <sabrecat@gmail.com>
2025-02-11 12:04:28 -06:00
Phillip Thelen
72a753626f Fix news related permission issues (#15287) 2025-02-11 12:04:00 -06:00
Phillip Thelen
35ebb12bf2 Fix achievement display in admin panel (#15326) 2025-02-11 12:02:50 -06:00
Kalista Payne
1ff418f62d 5.33.2 2025-02-06 13:53:00 -06:00
Weblate
e1aa437ea5 Translated using Weblate (German)
Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Portuguese)

Currently translated at 96.4% (864 of 896 strings)

Co-authored-by: Miya <baddybadges@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt/
Translation: Habitica/Backgrounds
2025-02-06 20:48:32 +01:00
Kalista Payne
2a4239bf3c fix(links): next round of wiki revisions 2025-02-05 09:18:22 -06:00
Kalista Payne
399563435b 5.33.1 2025-02-05 09:08:26 -06:00
Weblate
59f7e25c85 Translated using Weblate (Spanish)
Currently translated at 100.0% (821 of 821 strings)

Translated using Weblate (German)

Currently translated at 100.0% (821 of 821 strings)

Translated using Weblate (Japanese)

Currently translated at 87.0% (228 of 262 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 93.8% (107 of 114 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 18.1% (44 of 243 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 11.9% (29 of 243 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 88.1% (724 of 821 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 91.2% (104 of 114 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 7.4% (18 of 243 strings)

Translated using Weblate (Spanish)

Currently translated at 99.5% (817 of 821 strings)

Translated using Weblate (German)

Currently translated at 99.3% (816 of 821 strings)

Translated using Weblate (German)

Currently translated at 100.0% (3265 of 3265 strings)

Translated using Weblate (German)

Currently translated at 100.0% (3265 of 3265 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 98.2% (112 of 114 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 97.7% (131 of 134 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 69.1% (2257 of 3265 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 99.5% (239 of 240 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 16.4% (40 of 243 strings)

Translated using Weblate (German)

Currently translated at 99.9% (3264 of 3265 strings)

Translated using Weblate (Japanese)

Currently translated at 86.6% (227 of 262 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (272 of 272 strings)

Translated using Weblate (Japanese)

Currently translated at 97.9% (423 of 432 strings)

Translated using Weblate (German)

Currently translated at 100.0% (262 of 262 strings)

Translated using Weblate (German)

Currently translated at 100.0% (432 of 432 strings)

Translated using Weblate (German)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 14.8% (36 of 243 strings)

Translated using Weblate (German)

Currently translated at 99.1% (814 of 821 strings)

Translated using Weblate (German)

Currently translated at 100.0% (397 of 397 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 84.7% (222 of 262 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 84.3% (221 of 262 strings)

Translated using Weblate (German)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (22 of 22 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 96.0% (415 of 432 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 94.2% (3077 of 3265 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 63.7% (155 of 243 strings)

Translated using Weblate (German)

Currently translated at 99.0% (813 of 821 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 99.7% (396 of 397 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.7% (885 of 896 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (German)

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Japanese)

Currently translated at 97.4% (265 of 272 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (397 of 397 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (60 of 60 strings)

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Japanese)

Currently translated at 98.7% (392 of 397 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (262 of 262 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (432 of 432 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Spanish)

Currently translated at 99.0% (813 of 821 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (397 of 397 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (French)

Currently translated at 100.0% (262 of 262 strings)

Translated using Weblate (French)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (French)

Currently translated at 100.0% (432 of 432 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3265 of 3265 strings)

Translated using Weblate (French)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (French)

Currently translated at 100.0% (821 of 821 strings)

Translated using Weblate (French)

Currently translated at 100.0% (397 of 397 strings)

Translated using Weblate (French)

Currently translated at 100.0% (896 of 896 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3265 of 3265 strings)

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (French)

Currently translated at 100.0% (3265 of 3265 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3255 of 3255 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (15 of 15 strings)

Co-authored-by: Asier Gallego <agr2367789@gmail.com>
Co-authored-by: Asier Gallego Roca <asiernoide@users.noreply.translate.habitica.com>
Co-authored-by: Henrique Ferreira <pedroferreira217.ph@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: John Doe (Anonymous) <shyamjayeshduck@duck.com>
Co-authored-by: Katharina <katharinaanna.wilding@gmail.com>
Co-authored-by: Marie Blosse--Gilbin <mbgil@hotmail.fr>
Co-authored-by: Mauricio Pérez <mauriciodavidperez@gmail.com>
Co-authored-by: Raul Ernesto Ceron Lara <raztreuzz1234@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Willhelm Winter <carapax@posteo.de>
Co-authored-by: mattya 226 <worldworld1114@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/
Translate-URL: https://translate.habitica.com/projects/habitica/content/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/content/es/
Translate-URL: https://translate.habitica.com/projects/habitica/content/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/content/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/death/es/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/de/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/es/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/de/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/es/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/de/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/es/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/es/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/es/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt_BR/
Translation: Habitica/Backgrounds
Translation: Habitica/Content
Translation: Habitica/Death
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Loginincentives
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Pets
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Subscriber
2025-02-05 04:34:26 +01:00
Kalista Payne
ad845dff43 fix(test): nudge expected no-promo date 2025-01-29 17:17:28 -06:00
Kalista Payne
fd1eb2d900 feat(events): revise dates of promos 2025-01-29 16:59:14 -06:00
Kalista Payne
26cb6df9d9 5.33.0 2025-01-28 09:33:31 -06:00
klim
b0aafb079a Translated using Weblate (Russian)
Currently translated at 100.0% (167 of 167 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/
2025-01-28 16:31:17 +01:00
Weblate
58f0837c50 Translated using Weblate (Russian)
Currently translated at 100.0% (167 of 167 strings)

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 86.4% (210 of 243 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 87.6% (783 of 893 strings)

Translated using Weblate (French)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (French)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (Indonesian)

Currently translated at 95.9% (857 of 893 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 77.6% (2526 of 3255 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 86.9% (227 of 261 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.2% (133 of 134 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 97.4% (419 of 430 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (German)

Currently translated at 100.0% (430 of 430 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (German)

Currently translated at 100.0% (243 of 243 strings)

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Spanish (Latin America))

Currently translated at 42.8% (39 of 91 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 94.2% (229 of 243 strings)

Translated using Weblate (Russian)

Currently translated at 93.9% (763 of 812 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 88.4% (215 of 243 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 79.8% (194 of 243 strings)

Translated using Weblate (German)

Currently translated at 99.2% (133 of 134 strings)

Translated using Weblate (German)

Currently translated at 100.0% (3255 of 3255 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 79.4% (193 of 243 strings)

Translated using Weblate (German)

Currently translated at 100.0% (243 of 243 strings)

Translated using Weblate (German)

Currently translated at 100.0% (812 of 812 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.7% (393 of 394 strings)

Translated using Weblate (German)

Currently translated at 98.8% (3217 of 3255 strings)

Translated using Weblate (German)

Currently translated at 98.8% (3217 of 3255 strings)

Translated using Weblate (Indonesian)

Currently translated at 74.4% (181 of 243 strings)

Translated using Weblate (Indonesian)

Currently translated at 80.2% (73 of 91 strings)

Translated using Weblate (German)

Currently translated at 100.0% (432 of 432 strings)

Translated using Weblate (German)

Currently translated at 98.6% (3211 of 3255 strings)

Translated using Weblate (French)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (French)

Currently translated at 100.0% (432 of 432 strings)

Translated using Weblate (French)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (German)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (German)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (German)

Currently translated at 100.0% (260 of 260 strings)

Translated using Weblate (German)

Currently translated at 98.2% (3198 of 3255 strings)

Translated using Weblate (German)

Currently translated at 98.1% (3195 of 3255 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3255 of 3255 strings)

Co-authored-by: @hb-8zzgfb5cn8qla6olm <sinconexion3@gmail.com>
Co-authored-by: Aditya Setyo Lutfiandhika <sladityaa176@gmail.com>
Co-authored-by: Felix Yan <felixonmars@archlinux.org>
Co-authored-by: Happy Knight <2953467684@qq.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Katharina <katharinaanna.wilding@gmail.com>
Co-authored-by: LPDJ <petits.julius@gmail.com>
Co-authored-by: Natalia <nati.love.bill@gmail.com>
Co-authored-by: Nebula <habitica-translate.utopia411@passinbox.com>
Co-authored-by: Nisa Hadisti <nisahadisti18@gmail.com>
Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: Sarah <sarah.huang063@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: klim <petrrudichev@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/he/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/id/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/id/
Translate-URL: https://translate.habitica.com/projects/habitica/content/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/id/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/de/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/es/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/de/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/es/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/es/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hans/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Faq
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Pets
Translation: Habitica/Questscontent
Translation: Habitica/Settings
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2025-01-28 16:31:07 +01:00
Kalista Payne
a6378b3d43 fix(wiki): use new API Guidelines link 2025-01-27 19:37:38 -06:00
Kalista Payne
ddbf95da92 Squashed commit of the following:
commit 78fe7721a373effbfe62f31583296ee02c050a92
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Jan 15 15:16:56 2025 -0500

    fix: typo fix

commit 6a73cb247aff59102f91171f52c1d56f87bff562
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Jan 15 14:51:00 2025 -0500

    build: February 2025 content build
2025-01-23 15:33:16 -06:00
Kalista Payne
4d31e0286b chore(images): update subproj and CSS 2025-01-23 13:26:00 -06:00
Phillip Thelen
7a74825121 Remove references of certain social site for obvious reasons 2025-01-23 12:52:35 -06:00
Kalista Payne
be0e8779d5 fix(links): correct Guilds FAQ redirect and Contact Us 2025-01-23 12:50:03 -06:00
Kalista Payne
fffbe17bcc Squashed commit of the following:
commit 3746ccb2fdfb23276f49a9aee25e00ca366be14c
Author: Phillip Thelen <phillip@habitica.com>
Date:   Thu Jan 23 17:30:31 2025 +0100

    fix giving gear to contributors

commit 557cb582df47abb75331794e2af5c69da5548a90
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Jan 21 11:10:29 2025 +0100

    Give contributors gear immediately

commit 8d25bef6e1c6e48aa4d5a3b0cde49844d3164ed9
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Jan 21 11:10:16 2025 +0100

    adjust contributor tests

commit d918738533fe059db65d9020adb0126b43aaf0b3
Author: Phillip Thelen <phillip@habitica.com>
Date:   Tue Jan 21 11:02:19 2025 +0100

    adjust gems per tier
2025-01-23 12:41:06 -06:00
Rafał Jagielski
ca4ee8b513 Fix intro guide (#15247)
* Provide window.jquery to modules in vue.config.js

* Fix curly-spacing lint error
2025-01-23 12:27:26 -06:00
Kalista Payne
30f1820a49 5.32.5 2025-01-09 09:02:14 -06:00
Weblate
3bb6c391af Translated using Weblate (German)
Currently translated at 98.0% (3190 of 3255 strings)

Translated using Weblate (German)

Currently translated at 97.8% (3185 of 3255 strings)

Translated using Weblate (German)

Currently translated at 97.7% (3181 of 3255 strings)

Translated using Weblate (German)

Currently translated at 100.0% (261 of 261 strings)

Translated using Weblate (German)

Currently translated at 97.5% (3176 of 3255 strings)

Co-authored-by: Katharina <katharinaanna.wilding@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translation: Habitica/Gear
Translation: Habitica/Subscriber
2025-01-09 14:46:54 +01:00
Kalista Payne
a0383c785a Squashed commit of the following:
commit 5d3713008dc3041f63b23b22196e1ed79fab45bd
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jan 8 14:39:27 2025 -0600

    fix(text): pet Pet

commit 3ff5d7afeb517ae3d0933d8e18045b24575bb90b
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jan 8 14:34:02 2025 -0600

    fix(links): remove unnecessary style and icon

commit 5023dd9258aa0f9416daa212ed1249db2e5d5fe4
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jan 8 09:43:17 2025 -0600

    fix(links): update task modal markdown help

commit 81ebb279cd881d1c560668c3cb03600d85b9f5b9
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Wed Jan 8 09:26:11 2025 -0600

    fix(misc): clean up some layout and one more link

commit 2fd216f01531052aa769738f8fe1956ca6943822
Author: Kalista Payne <sabrecat@gmail.com>
Date:   Tue Jan 7 16:09:28 2025 -0600

    fix(links): remove/revise some outdated wiki links
2025-01-08 15:18:06 -06:00
Kalista Payne
99790c05f4 5.32.4 2025-01-06 16:22:26 -06:00
Weblate
fc5fec9bfe Merge branch 'origin/develop' into Weblate. 2025-01-06 23:18:24 +01:00
Phillip Thelen
9db5d4116d Fix availability of december background (#15378) 2025-01-06 15:57:57 -06:00
Weblate
6676e94ef6 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 95.7% (134 of 140 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Italian)

Currently translated at 9.4% (23 of 243 strings)

Translated using Weblate (Italian)

Currently translated at 99.3% (887 of 893 strings)

Translated using Weblate (Italian)

Currently translated at 99.3% (887 of 893 strings)

Translated using Weblate (Russian)

Currently translated at 91.4% (2978 of 3255 strings)

Translated using Weblate (Portuguese)

Currently translated at 97.2% (177 of 182 strings)

Translated using Weblate (French)

Currently translated at 100.0% (261 of 261 strings)

Translated using Weblate (French)

Currently translated at 99.2% (259 of 261 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (261 of 261 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (3255 of 3255 strings)

Co-authored-by: Chase7-Diaphragm0-Jeeringly7-Smartly2-Drainer5 <linguists-commonwealth@silkylegs.aleeas.com>
Co-authored-by: Gabrielle Renoir <cococherierenoir@gmail.com>
Co-authored-by: Jaime Martí <jaumemarti77@icloud.com>
Co-authored-by: Kesabirria <kebsebastian@gmail.com>
Co-authored-by: Oscar Rodríguez Díaz <alexoscarcrd@gmail.com>
Co-authored-by: Raquel Pantojo de Souza Bachour <raquel.pantojo@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Val <3qes0hnzh@mozmail.com>
Co-authored-by: Vitaliia Reinberg <vitalia.reynberg@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/
Translate-URL: https://translate.habitica.com/projects/habitica/front/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/es/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/es_419/
Translation: Habitica/Backgrounds
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2025-01-06 22:52:42 +01:00
Kalista Payne
723adceb25 chore(migrations): cute default and updateOne 2025-01-06 15:43:07 -06:00
Kalista Payne
440d06da4a 5.32.3 2025-01-03 12:51:57 -06:00
Weblate
0ea84668a8 Merge branch 'origin/develop' into Weblate. 2025-01-03 19:49:25 +01:00
Kalista Payne
5893d8b9bb fix(subs): revise benefit strings for clarity 2025-01-03 12:22:51 -06:00
Weblate
2c799b9c07 Translated using Weblate (Spanish)
Currently translated at 100.0% (3255 of 3255 strings)

Translated using Weblate (German)

Currently translated at 97.1% (3161 of 3255 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (893 of 893 strings)

Co-authored-by: Anna Tunger <anna.tunger@icloud.com>
Co-authored-by: Diego Alejandro Rios Vasquez <diegovasquezcolombia@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/
Translation: Habitica/Backgrounds
Translation: Habitica/Gear
2025-01-03 04:41:47 +01:00
Kalista Payne
1550d9b4ee 5.32.2 2025-01-02 11:22:51 -06:00
Weblate
ade812b86d Merge branch 'origin/develop' into Weblate. 2025-01-02 18:19:49 +01:00
Phillip Thelen
62e6fbef61 Fix content end date if already in new year (#15376)
* Fix content end date if already in new year

* fix test
2025-01-02 11:15:04 -06:00
Weblate
67a0f8b65a Translated using Weblate (Korean)
Currently translated at 70.4% (629 of 893 strings)

Translated using Weblate (Korean)

Currently translated at 91.6% (153 of 167 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: widesky8 <widesky20@naver.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ko/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
2025-01-01 03:17:52 +01:00
Kalista Payne
aa432022d3 fix(script): replace deprecated update function 2024-12-30 14:58:52 -06:00
Kalista Payne
86fb3c1fd1 5.32.1 2024-12-30 09:23:08 -06:00
Weblate
ff2b4add8b Translated using Weblate (German)
Currently translated at 96.1% (3130 of 3255 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Korean)

Currently translated at 96.4% (110 of 114 strings)

Translated using Weblate (Korean)

Currently translated at 49.4% (90 of 182 strings)

Translated using Weblate (Korean)

Currently translated at 91.0% (152 of 167 strings)

Translated using Weblate (Korean)

Currently translated at 54.8% (1784 of 3255 strings)

Translated using Weblate (Korean)

Currently translated at 65.3% (170 of 260 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (German)

Currently translated at 95.8% (3119 of 3255 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (240 of 240 strings)

Translated using Weblate (Slovak)

Currently translated at 71.0% (577 of 812 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (394 of 394 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (140 of 140 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 79.3% (207 of 261 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 92.1% (398 of 432 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 4.9% (12 of 243 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 94.3% (182 of 193 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 95.4% (105 of 110 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 86.5% (773 of 893 strings)

Translated using Weblate (Russian)

Currently translated at 36.6% (89 of 243 strings)

Translated using Weblate (Russian)

Currently translated at 36.6% (89 of 243 strings)

Translated using Weblate (German)

Currently translated at 95.6% (3115 of 3255 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 77.7% (189 of 243 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (893 of 893 strings)

Translated using Weblate (German)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (German)

Currently translated at 95.1% (3096 of 3255 strings)

Translated using Weblate (French)

Currently translated at 100.0% (261 of 261 strings)

Translated using Weblate (German)

Currently translated at 100.0% (261 of 261 strings)

Translated using Weblate (French)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (German)

Currently translated at 100.0% (134 of 134 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3255 of 3255 strings)

Translated using Weblate (German)

Currently translated at 93.8% (3056 of 3255 strings)

Translated using Weblate (French)

Currently translated at 100.0% (893 of 893 strings)

Translated using Weblate (German)

Currently translated at 100.0% (893 of 893 strings)

Co-authored-by: Boni hahaha <chanrouber@gmail.com>
Co-authored-by: Filip Betko <filipbetko@gmail.com>
Co-authored-by: Hikari <sss840127@gmail.com>
Co-authored-by: Irina  Shcherbinina <cat3dcat007@gmail.com>
Co-authored-by: Katharina <katharinaanna.wilding@gmail.com>
Co-authored-by: Lancelot Liu <me@lancy.dev>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Wyatt <1697570085@qq.com>
Co-authored-by: razil <boss.razmarin@gmail.com>
Co-authored-by: 리슈레이 <rishyurei@gmail.com>
Co-authored-by: 횬 <gkfpxldk0424@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/content/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/front/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/sk/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hant/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Messages
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2024-12-30 16:06:20 +01:00
Kalista Payne
4ba73dfbec fix(notifications): don't error on same page 2024-12-19 15:33:23 -06:00
dependabot[bot]
e675ea9bd1 Bump send and express in /website/client (#15324)
Bumps [send](https://github.com/pillarjs/send) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Natalie <78037386+CuriousMagpie@users.noreply.github.com>
2024-12-19 14:19:16 -05:00
dependabot[bot]
9c27d86ced Bump body-parser and express (#15319)
Bumps [body-parser](https://github.com/expressjs/body-parser) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `body-parser` from 1.20.2 to 1.20.3
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3)

Updates `express` from 4.19.2 to 4.20.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: direct:production
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Natalie <78037386+CuriousMagpie@users.noreply.github.com>
2024-12-19 14:19:04 -05:00
dependabot[bot]
58ee81adfc Bump serve-static and express (#15318)
Bumps [serve-static](https://github.com/expressjs/serve-static) to 1.16.0 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `serve-static` from 1.15.0 to 1.16.0
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...1.16.0)

Updates `express` from 4.19.2 to 4.20.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0)

---
updated-dependencies:
- dependency-name: serve-static
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Natalie <78037386+CuriousMagpie@users.noreply.github.com>
2024-12-19 14:18:51 -05:00
dependabot[bot]
32c9904a6e Bump cookie and express (#15338)
Bumps [cookie](https://github.com/jshttp/cookie) to 0.7.1 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `cookie` from 0.6.0 to 0.7.1
- [Release notes](https://github.com/jshttp/cookie/releases)
- [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: cookie
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-19 13:43:21 -05:00
dependabot[bot]
b86e0a1549 Bump send and express (#15339)
Bumps [send](https://github.com/pillarjs/send) to 0.19.0 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-19 13:42:48 -05:00
dependabot[bot]
154ac9bb38 chore(deps): bump mongoose from 7.6.8 to 7.8.3 (#15374)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 7.6.8 to 7.8.3.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/7.8.3/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/7.6.8...7.8.3)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-19 13:28:20 -05:00
dependabot[bot]
a97060445a chore(deps): bump cookie and express in /website/client (#15375)
Bumps [cookie](https://github.com/jshttp/cookie) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `cookie` from 0.6.0 to 0.7.1
- [Release notes](https://github.com/jshttp/cookie/releases)
- [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1)

Updates `express` from 4.19.2 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.2)

---
updated-dependencies:
- dependency-name: cookie
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-19 13:28:01 -05:00
Kalista Payne
26b59de1de feat(migration): revised NYE script 2024-12-18 18:01:16 -06:00
Kalista Payne
21c8b00ef6 chore(subproj): update habitica-images 2024-12-18 17:33:34 -06:00
455 changed files with 16277 additions and 8219 deletions

View File

@@ -7,5 +7,14 @@ module.exports = {
rules: {
'prefer-regex-literals': 'warn',
'import/no-extraneous-dependencies': 'off',
'require-await': 'error',
},
overrides: [
{
files: ['migrations/**', 'gulp/**'], // Or *.test.js
rules: {
'require-await': 'off',
},
},
],
};

View File

@@ -1,6 +1,13 @@
name: Test
on: [push, pull_request]
on:
push:
branches-ignore:
- 'phillip/**'
- 'sabrecat/**'
- 'kalista/**'
- 'natalie/**'
pull_request:
permissions:
contents: read

2
.gitignore vendored
View File

@@ -47,5 +47,5 @@ webpack.webstorm.config
# mongodb replica set for local dev
mongodb-*.tgz
/mongodb-data
/mongodb-data*
/.nyc_output

View File

@@ -2,4 +2,4 @@
This webpage includes the documentation for version 3 of the [Habitica](https://habitica.com) API.
If you're developing a 3rd party tool that uses the Habitica API you should read the [Guidance for Comrades](https://habitica.fandom.com/wiki/Guidance_for_Comrades) and in particular the section called [Rules for Third-Party Tools](https://habitica.fandom.com/wiki/Guidance_for_Comrades#Rules_for_Third-Party_Tools) which includes suggestions on how to best use the API and the rules to follow when interacting with it.
If you're developing a 3rd party tool that uses the Habitica API, read the [API Usage Guidelines](https://github.com/HabitRPG/habitica/wiki/API-Usage-Guidelines), which describe how to be a responsible user of our server resources!

View File

@@ -93,5 +93,6 @@
"TRUSTED_DOMAINS": "localhost,https://habitica.com",
"TIME_TRAVEL_ENABLED": "false",
"DEBUG_ENABLED": "false",
"CONTENT_SWITCHOVER_TIME_OFFSET": 8
"CONTENT_SWITCHOVER_TIME_OFFSET": 8,
"SLOW_REQUEST_THRESHOLD": 1000
}

View File

@@ -64,6 +64,15 @@ function filterFile (file) {
if (file.relative.indexOf('icon_background') === 0) {
return false;
}
if (file.relative.indexOf('notif_') === 0) {
return false;
}
if (file.relative.indexOf('quest_') === 0) {
return false;
}
if (file.relative.indexOf('inventory_quest_') === 0) {
return false;
}
return true;
}

View File

@@ -26,7 +26,7 @@ async function updateUser (user) {
[{ name: 'BASE_URL', content: BASE_URL }], // Add variables from template
);
return User.update({ _id: user._id }, { $set: { migration: MIGRATION_NAME } }).exec();
return User.updateOne({ _id: user._id }, { $set: { migration: MIGRATION_NAME } }).exec();
}
export default async function processUsers () {

View File

@@ -27,13 +27,13 @@ async function updateUser (user) {
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return User.update({ _id: user._id }, { $set: set }).exec();
return User.updateOne({ _id: user._id }, { $set: set }).exec();
}
export default async function processUsers () {
const query = {
migration: { $ne: MIGRATION_NAME },
'auth.local.lowerCaseUsername': 'olson1',
'auth.local.username': 'ExampleHabitican',
};
const fields = {

View File

@@ -57,7 +57,7 @@ async function updateUser (user) {
export default async function processUsers () {
const query = {
migration: { $ne: MIGRATION_NAME },
'auth.local.username': 'SabreTest',
'auth.local.username': 'ExampleHabitican',
};
const fields = {

125
migrations/users/nye.js Normal file
View File

@@ -0,0 +1,125 @@
/* eslint-disable no-console */
import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = '20231228_nye';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count += 1;
const updateOp = {
$set: { migration: MIGRATION_NAME },
$push: { },
};
const data = {
title: 'Happy New Year!',
destination: '/inventory/equipment',
};
if (typeof user.items.gear.owned.head_special_nye2023 !== 'undefined') {
updateOp.$inc = {
'items.food.Candy_Skeleton': 1,
'items.food.Candy_Base': 1,
'items.food.Candy_CottonCandyBlue': 1,
'items.food.Candy_CottonCandyPink': 1,
'items.food.Candy_Shade': 1,
'items.food.Candy_White': 1,
'items.food.Candy_Golden': 1,
'items.food.Candy_Zombie': 1,
'items.food.Candy_Desert': 1,
'items.food.Candy_Red': 1,
};
data.icon = 'notif_candy_nye';
data.text = 'Youve received an assortment of candy to celebrate with your Pets!';
data.destination = '/inventory/stable';
} else if (typeof user.items.gear.owned.head_special_nye2022 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2023'] = true;
data.icon = 'notif_2023hat_nye';
data.text = 'Take on your resolutions with style in this Ludicrous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2021 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2022'] = true;
data.icon = 'notif_2022hat_nye';
data.text = 'Take on your resolutions with style in this Fabulous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2020 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2021'] = true;
data.icon = 'notif_2021hat_nye';
data.text = 'Take on your resolutions with style in this Preposterous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2019 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2020'] = true;
data.icon = 'notif_2020hat_nye';
data.text = 'Take on your resolutions with style in this Extravagant Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2018 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2019'] = true;
data.icon = 'notif_2019hat_nye';
data.text = 'Take on your resolutions with style in this Outrageous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2018'] = true;
data.icon = 'notif_2018hat_nye';
data.text = 'Take on your resolutions with style in this Outlandish Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2017'] = true;
data.icon = 'notif_2017hat_nye';
data.text = 'Take on your resolutions with style in this Fanciful Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2016'] = true;
data.icon = 'notif_2016hat_nye';
data.text = 'Take on your resolutions with style in this Whimsical Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2015'] = true;
data.icon = 'notif_2015hat_nye';
data.text = 'Take on your resolutions with style in this Ridiculous Party Hat!';
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
updateOp.$set['items.gear.owned.head_special_nye2014'] = true;
data.icon = 'notif_2014hat_nye';
data.text = 'Take on your resolutions with style in this Silly Party Hat!';
} else {
updateOp.$set['items.gear.owned.head_special_nye'] = true;
data.icon = 'notif_2013hat_nye';
data.text = 'Take on your resolutions with style in this Absurd Party Hat!';
}
updateOp.$push.notifications = {
type: 'ITEM_RECEIVED',
data,
seen: false,
};
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return User.updateOne({ _id: user._id }, updateOp).exec();
}
export default async function processUsers () {
const query = {
'auth.timestamps.loggedin': { $gt: new Date('2023-12-01') },
migration: { $ne: MIGRATION_NAME },
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({ _id: 1 })
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
}

202
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "habitica",
"version": "5.32.0",
"version": "5.35.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "habitica",
"version": "5.32.0",
"version": "5.35.1",
"hasInstallScript": true,
"dependencies": {
"@babel/core": "^7.22.10",
@@ -22,7 +22,7 @@
"apple-auth": "^1.0.9",
"babel-preset-env": "^1.7.0",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"body-parser": "^1.20.3",
"bootstrap": "^4.6.2",
"compression": "^1.7.4",
"cookie-session": "^2.0.0",
@@ -33,7 +33,7 @@
"eslint": "^8.55.0",
"eslint-config-habitrpg": "^6.2.3",
"eslint-plugin-mocha": "^5.0.0",
"express": "^4.19.2",
"express": "^4.21.1",
"express-basic-auth": "^1.2.1",
"express-validator": "^5.2.0",
"firebase-admin": "^12.1.1",
@@ -56,7 +56,7 @@
"method-override": "^3.0.0",
"moment": "^2.29.4",
"moment-recur": "^1.0.7",
"mongoose": "^7.6.3",
"mongoose": "^7.8.3",
"morgan": "^1.10.0",
"nconf": "^0.12.1",
"node-gcm": "^1.0.5",
@@ -3044,9 +3044,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz",
"integrity": "sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==",
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
"integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
"optional": true,
"dependencies": {
"sparse-bitfield": "^3.0.3"
@@ -6244,9 +6244,9 @@
"dev": true
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
@@ -6256,7 +6256,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@@ -6291,11 +6291,11 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/body-parser/node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@@ -6577,14 +6577,15 @@
}
},
"node_modules/call-bind": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz",
"integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.3",
"set-function-length": "^1.2.0"
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
@@ -7369,9 +7370,9 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
@@ -8376,9 +8377,9 @@
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": {
"node": ">= 0.8"
}
@@ -8497,6 +8498,17 @@
"resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
"integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="
},
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
@@ -9980,36 +9992,36 @@
}
},
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@@ -10062,11 +10074,11 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/express/node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@@ -10531,12 +10543,12 @@
}
},
"node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@@ -13348,10 +13360,27 @@
"resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz",
"integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA=="
},
"node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
"node_modules/ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
"dependencies": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/ip-address/node_modules/jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
},
"node_modules/ip-address/node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
@@ -14961,9 +14990,12 @@
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-stream": {
"version": "2.0.0",
@@ -15564,13 +15596,13 @@
}
},
"node_modules/mongoose": {
"version": "7.6.8",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.6.8.tgz",
"integrity": "sha512-q9zAySH+UtOK5yonWyNcLfq3PxrY6s4gdta4qNGKNOE2yTVoY9FP4hQtvWYnv4rkdk7T8QmQMC7bbhJjDxIunw==",
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.8.3.tgz",
"integrity": "sha512-eFnbkKgyVrICoHB6tVJ4uLanS7d5AIo/xHkEbQeOv6g2sD7gh/1biRwvFifsmbtkIddQVNr3ROqHik6gkknN3g==",
"dependencies": {
"bson": "^5.5.0",
"kareem": "2.5.1",
"mongodb": "5.9.1",
"mongodb": "5.9.2",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
@@ -15593,9 +15625,9 @@
}
},
"node_modules/mongoose/node_modules/mongodb": {
"version": "5.9.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.1.tgz",
"integrity": "sha512-NBGA8AfJxGPeB12F73xXwozt8ZpeIPmCUeWRwl9xejozTXFes/3zaep9zhzs1B/nKKsw4P3I4iPfXl3K7s6g+Q==",
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz",
"integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==",
"dependencies": {
"bson": "^5.5.0",
"mongodb-connection-string-url": "^2.6.0",
@@ -17592,9 +17624,9 @@
}
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
},
"node_modules/path-type": {
"version": "1.1.0",
@@ -19337,9 +19369,9 @@
}
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
@@ -19372,6 +19404,14 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -19386,14 +19426,14 @@
}
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dependencies": {
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -19519,11 +19559,11 @@
}
},
"node_modules/side-channel": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
"integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
"dependencies": {
"call-bind": "^1.0.6",
"call-bind": "^1.0.7",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.4",
"object-inspect": "^1.13.1"
@@ -19861,15 +19901,15 @@
}
},
"node_modules/socks": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
"integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
"integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
"dependencies": {
"ip": "^2.0.0",
"ip-address": "^9.0.5",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.13.0",
"node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "5.32.0",
"version": "5.35.1",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.22.10",
@@ -17,7 +17,7 @@
"apple-auth": "^1.0.9",
"babel-preset-env": "^1.7.0",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"body-parser": "^1.20.3",
"bootstrap": "^4.6.2",
"compression": "^1.7.4",
"cookie-session": "^2.0.0",
@@ -28,7 +28,7 @@
"eslint": "^8.55.0",
"eslint-config-habitrpg": "^6.2.3",
"eslint-plugin-mocha": "^5.0.0",
"express": "^4.19.2",
"express": "^4.21.1",
"express-basic-auth": "^1.2.1",
"express-validator": "^5.2.0",
"firebase-admin": "^12.1.1",
@@ -51,7 +51,7 @@
"method-override": "^3.0.0",
"moment": "^2.29.4",
"moment-recur": "^1.0.7",
"mongoose": "^7.6.3",
"mongoose": "^7.8.3",
"morgan": "^1.10.0",
"nconf": "^0.12.1",
"node-gcm": "^1.0.5",
@@ -110,6 +110,7 @@
"start:simple": "node ./website/server/index.js",
"debug": "gulp nodemon --inspect",
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
"mongo:test": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data-testing --number 1 --quiet",
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
"apidoc": "gulp apidoc",
"heroku-postbuild": ".heroku/report_deploy.sh"

View File

@@ -171,23 +171,23 @@ describe('emails', () => {
expect(got.post).not.to.be.called;
});
it('throws error when mail target is only a string', () => {
it('throws error when mail target is only a string', async () => {
const emailType = 'an email type';
const mailingInfo = 'my email';
expect(sendTxn(mailingInfo, emailType)).to.throw;
await expect(sendTxn(mailingInfo, emailType)).to.be.rejectedWith('Argument Error mailingInfoArray: does not contain email or _id');
});
it('throws error when mail target has no _id or email', () => {
it('throws error when mail target has no _id or email', async () => {
const emailType = 'an email type';
const mailingInfo = {
};
expect(sendTxn(mailingInfo, emailType)).to.throw;
await expect(sendTxn(mailingInfo, emailType)).to.be.rejectedWith('Argument Error mailingInfoArray: does not contain email or _id');
});
it('throws error when variables not an array', () => {
it('throws error when variables not an array', async () => {
const emailType = 'an email type';
const mailingInfo = {
name: 'my name',
@@ -195,9 +195,10 @@ describe('emails', () => {
};
const variables = {};
expect(sendTxn(mailingInfo, emailType, variables)).to.throw;
await expect(sendTxn(mailingInfo, emailType, variables)).to.be.rejectedWith('Argument Error variables: is not an array');
});
it('throws error when variables array not contain name/content', () => {
it('throws error when variables array not contain name/content', async () => {
const emailType = 'an email type';
const mailingInfo = {
name: 'my name',
@@ -209,8 +210,9 @@ describe('emails', () => {
},
];
expect(sendTxn(mailingInfo, emailType, variables)).to.throw;
await expect(sendTxn(mailingInfo, emailType, variables)).to.be.rejectedWith('Argument Error variables: does not contain name or content');
});
it('throws no error when variables array contain name but no content', () => {
const emailType = 'an email type';
const mailingInfo = {

View File

@@ -59,7 +59,7 @@ describe('POST /debug/jump-time', () => {
expect(resultDate.getDate()).to.eql(today.getDate());
expect(resultDate.getMonth()).to.eql(today.getMonth());
expect(resultDate.getFullYear()).to.eql(today.getFullYear());
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 355 })).time);
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 365 })).time);
expect(newResultDate.getFullYear()).to.eql(today.getFullYear() + 1);
});

View File

@@ -10,6 +10,7 @@ describe('GET /heroes/:heroId', () => {
const heroFields = [
'_id', 'id', 'auth', 'balance', 'contributor', 'flags', 'items',
'lastCron', 'party', 'preferences', 'profile', 'purchased', 'secret', 'achievements',
'stats',
];
before(async () => {

View File

@@ -11,6 +11,7 @@ describe('PUT /heroes/:heroId', () => {
const heroFields = [
'_id', 'auth', 'balance', 'contributor', 'flags', 'items', 'lastCron',
'party', 'preferences', 'profile', 'purchased', 'secret', 'permissions', 'achievements',
'stats',
];
before(async () => {
@@ -60,12 +61,12 @@ describe('PUT /heroes/:heroId', () => {
expect(heroRes.profile).to.have.all.keys(['name']);
// test response values
expect(heroRes.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level
expect(heroRes.balance).to.equal(3 + 2.5); // 3+2.5 for first contrib level
expect(heroRes.contributor.level).to.equal(1);
expect(heroRes.purchased.ads).to.equal(true);
// test hero values
await hero.sync();
expect(hero.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level
expect(hero.balance).to.equal(3 + 2.5); // 3+2.5 for first contrib level
expect(hero.contributor.level).to.equal(1);
expect(hero.purchased.ads).to.equal(true);
expect(hero.auth.blocked).to.equal(prevBlockState);
@@ -136,12 +137,12 @@ describe('PUT /heroes/:heroId', () => {
expect(heroRes.profile).to.have.all.keys(['name']);
// test response values
expect(heroRes.balance).to.equal(1); // 0+1 for sixth contrib level
expect(heroRes.balance).to.equal(15); // 0+15 for sixth contrib level
expect(heroRes.contributor.level).to.equal(6);
expect(heroRes.items.pets['Dragon-Hydra']).to.equal(5);
// test hero values
await hero.sync();
expect(hero.balance).to.equal(1); // 0+1 for sixth contrib level
expect(hero.balance).to.equal(15); // 0+15 for sixth contrib level
expect(hero.contributor.level).to.equal(6);
expect(hero.items.pets['Dragon-Hydra']).to.equal(5);
});

View File

@@ -0,0 +1,56 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import common from '../../../../../website/common';
describe('GET /members/username/:username', () => {
let user;
before(async () => {
user = await generateUser();
});
it('validates req.params.username', async () => {
await expect(user.get('/members/username/')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns a member\'s public data only', async () => {
// make sure user has all the fields that can be returned by the getMember call
const member = await generateUser({
contributor: { level: 1 },
backer: { tier: 3 },
preferences: {
costume: false,
background: 'volcano',
},
secret: {
text: 'Clark Kent',
},
});
const memberRes = await user.get(`/members/username/${member.auth.local.username}`);
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
]);
expect(Object.keys(memberRes.auth)).to.eql(['local', 'timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql([
'size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
].sort());
expect(memberRes.stats.maxMP).to.exist;
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
expect(memberRes.stats.toNextLevel).to.equal(common.tnl(memberRes.stats.lvl));
expect(memberRes.inbox.optOut).to.exist;
expect(memberRes.inbox.canReceive).to.exist;
expect(memberRes.inbox.messages).to.not.exist;
expect(memberRes.secret).to.not.exist;
expect(memberRes.blocks).to.not.exist;
});
});

View File

@@ -0,0 +1,104 @@
import find from 'lodash/find';
import {
generateUser,
translate as t,
} from '../../../helpers/api-integration/v4';
/**
* Checks the messages array if the uniqueMessageId has the like flag
* @param {InboxMessage[]} messages
* @param {String} uniqueMessageId
* @param {String} userId
* @param {Boolean} likeStatus
*/
function expectMessagesLikeStatus (messages, uniqueMessageId, userId, likeStatus) {
const messageToCheck = find(messages, { uniqueMessageId });
expect(messageToCheck.likes[userId]).to.equal(likeStatus);
}
// eslint-disable-next-line mocha/no-exclusive-tests
describe('POST /inbox/like-private-message/:messageId', () => {
let userToSendMessage;
const getLikeUrl = messageId => `/inbox/like-private-message/${messageId}`;
before(async () => {
userToSendMessage = await generateUser();
});
it('returns an error when private message is not found', async () => {
await expect(userToSendMessage.post(getLikeUrl('some-unknown-id')))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatNotFound'),
});
});
it('likes a message', async () => {
const receiver = await generateUser();
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
message: 'some message :)',
toUserId: receiver._id,
});
const { uniqueMessageId } = sentMessageResult.message;
const likeResult = await receiver.post(getLikeUrl(uniqueMessageId));
expect(likeResult.likes[receiver._id]).to.equal(true);
const senderMessages = await userToSendMessage.get('/inbox/messages');
expectMessagesLikeStatus(senderMessages, uniqueMessageId, receiver._id, true);
const receiversMessages = await receiver.get('/inbox/messages');
expectMessagesLikeStatus(receiversMessages, uniqueMessageId, receiver._id, true);
});
it('allows a user to like their own private message', async () => {
const receiver = await generateUser();
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
message: 'some message :)',
toUserId: receiver._id,
});
const { uniqueMessageId } = sentMessageResult.message;
const likeResult = await userToSendMessage.post(getLikeUrl(uniqueMessageId));
expect(likeResult.likes[userToSendMessage._id]).to.equal(true);
const messages = await userToSendMessage.get('/inbox/messages');
expectMessagesLikeStatus(messages, uniqueMessageId, userToSendMessage._id, true);
const receiversMessages = await receiver.get('/inbox/messages');
expectMessagesLikeStatus(receiversMessages, uniqueMessageId, userToSendMessage._id, true);
});
it('unlikes a message', async () => {
const receiver = await generateUser();
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
message: 'some message :)',
toUserId: receiver._id,
});
const { uniqueMessageId } = sentMessageResult.message;
const likeResult = await receiver.post(getLikeUrl(uniqueMessageId));
expect(likeResult.likes[receiver._id]).to.equal(true);
const unlikeResult = await receiver.post(getLikeUrl(uniqueMessageId));
expect(unlikeResult.likes[receiver._id]).to.equal(false);
const messages = await userToSendMessage.get('/inbox/messages');
const messageToCheck = find(messages, { id: sentMessageResult.message.id });
expect(messageToCheck.likes[receiver._id]).to.equal(false);
});
});

View File

@@ -10,7 +10,7 @@ describe('events', () => {
});
it('returns empty array when no events are active', () => {
clock = sinon.useFakeTimers(new Date('2024-01-08'));
clock = sinon.useFakeTimers(new Date('2024-01-11'));
const events = getRepeatingEvents();
expect(events).to.be.empty;
});

View File

@@ -144,6 +144,12 @@ describe('Content Schedule', () => {
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
});
it('sets the end date in new year for a winter gala', () => {
const date = new Date('2025-01-04');
const matchers = getAllScheduleMatchingGroups(date);
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
});
it('uses correct date for first hours of the month', () => {
// if the date is checked before CONTENT_SWITCHOVER_TIME_OFFSET,
// it should be considered the previous month
@@ -184,7 +190,7 @@ describe('Content Schedule', () => {
const date = new Date('2024-04-15');
const matchers = getAllScheduleMatchingGroups(date);
expect(matchers.premiumHatchingPotions).to.exist;
expect(matchers.premiumHatchingPotions.items.length).to.equal(5);
expect(matchers.premiumHatchingPotions.items.length).to.equal(6);
expect(matchers.premiumHatchingPotions.items.indexOf('Veggie')).to.not.equal(-1);
expect(matchers.premiumHatchingPotions.items.indexOf('Porcelain')).to.not.equal(-1);
});
@@ -266,6 +272,21 @@ describe('Content Schedule', () => {
expect(matcher.match('backgroundkey072024')).to.be.true;
});
it('allows background matching the month for new backgrounds from multiple years', () => {
const date = new Date('2026-07-08');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey072024')).to.be.true;
expect(matcher.match('backgroundkey072025')).to.be.true;
expect(matcher.match('backgroundkey072026')).to.be.true;
});
it('allows background matching the previous month in the first week for new backgrounds', () => {
const date = new Date('2024-09-02');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey082024')).to.be.true;
expect(matcher.match('backgroundkey092024')).to.be.false;
});
it('disallows background in the future', () => {
const date = new Date('2024-07-08');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
@@ -285,19 +306,26 @@ describe('Content Schedule', () => {
expect(matcher.match('backgroundkey022021')).to.be.true;
});
it('allows background even yeared backgrounds in first half of year', () => {
it('allows even yeared backgrounds in first half of year', () => {
const date = new Date('2025-02-08');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey022024')).to.be.true;
expect(matcher.match('backgroundkey082022')).to.be.true;
});
it('allows background odd yeared backgrounds in second half of year', () => {
it('allows odd yeared backgrounds in second half of year', () => {
const date = new Date('2024-08-08');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey022023')).to.be.true;
expect(matcher.match('backgroundkey082021')).to.be.true;
});
it('allows odd yeared backgrounds in beginning of january', () => {
const date = new Date('2025-01-06');
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
expect(matcher.match('backgroundkey122024'), 'backgroundkey122024').to.be.true;
expect(matcher.match('backgroundkey062023'), 'backgroundkey062022').to.be.true;
});
});
describe('timeTravelers matcher', () => {

View File

@@ -7,7 +7,7 @@ module.exports = {
extends: [
'habitrpg/lib/vue',
],
ignorePatterns: ['dist/', 'node_modules/'],
ignorePatterns: ['dist/', 'node_modules/', '*.d.ts'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',

View File

@@ -3934,9 +3934,9 @@
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
@@ -3946,7 +3946,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@@ -4140,6 +4140,33 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz",
"integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"get-intrinsic": "^1.2.6"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -4616,9 +4643,9 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
@@ -5422,6 +5449,19 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"node_modules/dunder-proto": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz",
"integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@@ -5464,9 +5504,9 @@
}
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": {
"node": ">= 0.8"
}
@@ -5588,12 +5628,9 @@
}
},
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"engines": {
"node": ">= 0.4"
}
@@ -5611,6 +5648,17 @@
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
"integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
},
"node_modules/es-object-atoms": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
@@ -6695,36 +6743,36 @@
}
},
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@@ -6733,6 +6781,10 @@
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express/node_modules/array-flatten": {
@@ -6877,12 +6929,12 @@
}
},
"node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@@ -7125,15 +7177,20 @@
}
},
"node_modules/get-intrinsic": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz",
"integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"dunder-proto": "^1.0.0",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.0.0",
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
@@ -7253,11 +7310,11 @@
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dependencies": {
"get-intrinsic": "^1.1.3"
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -7353,9 +7410,9 @@
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"engines": {
"node": ">= 0.4"
},
@@ -7383,9 +7440,9 @@
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="
},
"node_modules/hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -8919,6 +8976,14 @@
"markdown-it": "bin/markdown-it.js"
}
},
"node_modules/math-intrinsics": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz",
"integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mdn-data": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
@@ -8958,9 +9023,12 @@
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-source-map": {
"version": "1.1.0",
@@ -9862,9 +9930,12 @@
}
},
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
"integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -10310,9 +10381,9 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
},
"node_modules/path-type": {
"version": "4.0.0",
@@ -11129,11 +11200,11 @@
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@@ -11736,9 +11807,9 @@
"dev": true
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
@@ -11771,6 +11842,14 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -11855,14 +11934,14 @@
}
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dependencies": {
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -11951,13 +12030,68 @@
}
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"

View File

@@ -22,7 +22,8 @@
height: 219px;
}
.Pet_HatchingPotion_Dessert, .Pet_HatchingPotion_Veggie, .Pet_HatchingPotion_Windup, .Pet_HatchingPotion_VirtualPet, .Pet_HatchingPotion_Fungi {
.Pet_HatchingPotion_Dessert, .Pet_HatchingPotion_Veggie, .Pet_HatchingPotion_Windup,
.Pet_HatchingPotion_VirtualPet, .Pet_HatchingPotion_Fungi, .Pet_HatchingPotion_Cryptid {
width: 68px;
height: 68px;
}
@@ -47,6 +48,10 @@
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Fungi.gif") no-repeat;
}
.Pet_HatchingPotion_Cryptid {
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Cryptid.gif") no-repeat;
}
.Gems {
display:inline-block;
margin-right:5px;

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@
top: -16px !important;
}
$foolPets: Veggie, Dessert, VirtualPet, TeaShop, Fungi;
$foolPets: Veggie, Dessert, VirtualPet, TeaShop, Fungi, Cryptid;
@each $foolPet in $foolPets {
.Pet.Pet-FlyingPig-#{$foolPet} {

View File

@@ -101,8 +101,7 @@
.btn-secondary,
.dropdown > .btn-secondary.dropdown-toggle:not(.btn-success),
.show > .btn-secondary.dropdown-toggle:not(.btn-success)
{
.show > .btn-secondary.dropdown-toggle:not(.btn-success) {
background: $white;
border: 2px solid transparent;
color: $gray-50;
@@ -298,6 +297,16 @@
box-shadow: none;
}
.btn-flat,
.dropdown > .btn-flat.dropdown-toggle:not(.btn-success),
.show > .btn-flat.dropdown-toggle:not(.btn-success) {
&.with-icon {
.svg-icon.color {
color: var(--icon-color);
}
}
}
.btn-cancel {
color: $blue-10;
}

View File

@@ -38,7 +38,12 @@
border-radius: 2px;
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
padding: 0;
}
.no-min-width {
.dropdown-menu {
min-width: 0 !important;
}
}
// shared dropdown-item styles
@@ -54,6 +59,8 @@
color: $gray-50 !important;
cursor: pointer;
--dropdown-item-hover-icon-color: #{$gray-200};
&:focus {
outline: none;
background-color: inherit;
@@ -88,7 +95,7 @@
&:not(:hover) {
.with-icon .svg-icon {
color: $gray-200;
color: var(dropdown-item-hover-icon-color);
}
}
}
@@ -151,7 +158,7 @@
// selectList.vue items sizing
.selectListItem .dropdown-item {
padding: 0.25rem 0.75rem;
padding: 0.25rem 1rem 0.25rem 0.75rem;
height: 32px;
&:active, &:hover, &:focus, &.active {

View File

@@ -0,0 +1,3 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M4,0C1.79,0,0,1.79,0,4v16c0,2.21,1.79,4,4,4h16c2.21,0,4-1.79,4-4V4c0-2.21-1.79-4-4-4H4ZM12,11.57c-.72-1.49-2.7-4.26-4.53-5.63-1.32-.99-3.47-1.75-3.47.68,0,.49.28,4.08.44,4.66.57,2.03,2.65,2.55,4.5,2.23-3.24.55-4.06,2.36-2.28,4.17,3.38,3.44,4.85-.86,5.23-1.97h0s0,0,0,0c.07-.2.1-.29.1-.21,0-.08.03.01.1.22h0c.38,1.1,1.85,5.41,5.23,1.97,1.78-1.81.95-3.63-2.28-4.17,1.85.31,3.93-.2,4.5-2.23.16-.58.44-4.18.44-4.66,0-2.43-2.14-1.67-3.47-.68-1.83,1.37-3.81,4.14-4.53,5.63Z" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 572 B

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M20,0H4A4,4,0,0,0,0,4V20a4,4,0,0,0,4,4H20a4,4,0,0,0,4-4V4A4,4,0,0,0,20,0ZM18.36,8.74c0,.14,0,.29,0,.43A9.34,9.34,0,0,1,4,17a6.85,6.85,0,0,0,.79,0,6.57,6.57,0,0,0,4.07-1.4A3.29,3.29,0,0,1,5.8,13.39a4.1,4.1,0,0,0,.62,0,3.49,3.49,0,0,0,.86-.11,3.28,3.28,0,0,1-2.63-3.22v0a3.35,3.35,0,0,0,1.48.42A3.29,3.29,0,0,1,4.67,7.76,3.22,3.22,0,0,1,5.12,6.1a9.3,9.3,0,0,0,6.76,3.43,3.67,3.67,0,0,1-.08-.75,3.28,3.28,0,0,1,5.67-2.24,6.54,6.54,0,0,0,2.08-.79,3.22,3.22,0,0,1-1.44,1.8A6.67,6.67,0,0,0,20,7.05,7.31,7.31,0,0,1,18.36,8.74Z" fill-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 622 B

View File

@@ -25,9 +25,9 @@
<router-link to="/">
Homepage
</router-link>or
<router-link :to="contactUsLink">
<a href="mailto:admin@habitica.com">
Contact Us
</router-link>about the issue.
</a>about the issue.
</p>
</div>
</div>
@@ -40,12 +40,6 @@ import { mapState } from '@/libs/store';
export default {
computed: {
...mapState(['isUserLoggedIn']),
contactUsLink () {
if (this.isUserLoggedIn) {
return { name: 'guild', params: { groupId: 'a29da26b-37de-4a71-b0c6-48e72a900dac' } };
}
return { name: 'contact' };
},
retiredChatPage () {
return this.$route.fullPath.indexOf('/groups') !== -1;
},

View File

@@ -8,7 +8,7 @@
<div class="modal-body">
<div class="row">
<div class="col-6 offset-3">
<div class="shop_armoire"></div>
<Sprite image-name="shop_armoire" />
<p>{{ $t('armoireLastItem') }}</p>
<p>{{ $t('armoireNotesEmpty') }}</p>
</div>
@@ -34,7 +34,12 @@
</style>
<script>
import Sprite from '@/components/ui/sprite';
export default {
components: {
Sprite,
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'armoire-empty');

View File

@@ -95,7 +95,11 @@
@click="clickDisableClasses(); close();"
>{{ $t('optOutOfClasses') }}</span>
</div>
<span class="opt-out-description">{{ $t('optOutOfClassesText') }}</span>
<div
v-once
class="opt-out-description"
v-html="$t('optOutOfClassesText')"
></div>
</div>
</div>
</div>

View File

@@ -48,7 +48,7 @@
></span>
</div>
<div :class="questClass"></div>
<Sprite :image-name="questClass" />
</section>
<!-- @TODO: Keep this? .checkboxinput(type='checkbox', v-model=
'user.preferences.suppressModals.levelUp', @change='changeLevelupSuppress()')
@@ -150,15 +150,12 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
section.greyed {
padding-bottom: 17px
}
.scroll {
margin: -11px auto 0;
}
}
</style>
<script>
import Avatar from '../avatar';
import Sprite from '@/components/ui/sprite';
import { mapState } from '@/libs/store';
import starGroup from '@/assets/svg/star-group.svg';
import sparkles from '@/assets/svg/sparkles-left.svg';
@@ -173,6 +170,7 @@ const levelQuests = {
export default {
components: {
Avatar,
Sprite,
},
data () {
return {
@@ -191,7 +189,9 @@ export default {
return this.user.stats.lvl in levelQuests;
},
questClass () {
return `scroll inventory_quest_scroll_${levelQuests[this.user.stats.lvl]}`;
const questKey = levelQuests[this.user.stats.lvl];
if (questKey) return `inventory_quest_scroll_${questKey}`;
return '';
},
},
methods: {

View File

@@ -55,7 +55,7 @@
<p v-html="$t('moreGearAchievements')"></p>
<br>
</div>
<div class="shop_armoire"></div>
<Sprite image-name="shop_armoire" />
<p v-html="$t('armoireUnlocked')"></p>
<br>
<button
@@ -87,11 +87,13 @@
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import { mapState } from '@/libs/store';
import Sprite from '@/components/ui/sprite.vue';
export default {
components: {
achievementFooter,
achievementAvatar,
Sprite,
},
computed: {
...mapState({ user: 'user.data' }),

View File

@@ -92,8 +92,6 @@ export default {
params: { userIdentifier },
}).catch(failure => {
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
// the admin has requested that the same user be displayed again so reload the page
// (e.g., if they changed their mind about changes they were making)
this.$router.go();
}
});
@@ -101,14 +99,16 @@ export default {
async loadUser (userIdentifier) {
const id = userIdentifier || this.user._id;
this.$router.push({
if (this.$router.currentRoute.name === 'adminPanelUser') {
await this.$router.push({
name: 'adminPanel',
});
}
await this.$router.push({
name: 'adminPanelUser',
params: { userIdentifier: id },
}).catch(failure => {
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
// the admin has requested that the same user be displayed again so reload the page
// (e.g., if they changed their mind about changes they were making)
this.$router.go();
}
});

View File

@@ -1,6 +1,15 @@
import VueRouter from 'vue-router';
const { isNavigationFailure, NavigationFailureType } = VueRouter;
export default {
methods: {
async saveHero ({ hero, msg = 'User', clearData }) {
async saveHero ({
hero,
msg = 'User',
clearData,
reloadData,
}) {
await this.$store.dispatch('hall:updateHero', { heroDetails: hero });
await this.$store.dispatch('snackbars:add', {
title: '',
@@ -14,6 +23,20 @@ export default {
// The admin should re-fetch the data if they need to keep working on that user.
this.$emit('clear-data');
this.$router.push({ name: 'adminPanel' });
} else if (reloadData) {
if (this.$router.currentRoute.name === 'adminPanelUser') {
await this.$router.push({
name: 'adminPanel',
});
}
await this.$router.push({
name: 'adminPanelUser',
params: { userIdentifier: hero._id },
}).catch(failure => {
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
this.$router.go();
}
});
}
},
},

View File

@@ -7,7 +7,11 @@
>
Could not find any matching users.
</div>
<loading-spinner class="mx-auto mb-2" dark-color="true" v-if="isSearching" />
<loading-spinner
v-if="isSearching"
class="mx-auto mb-2"
dark-color="true"
/>
<div
v-if="users.length > 0"
class="list-group"
@@ -59,6 +63,10 @@ export default {
components: {
LoadingSpinner,
},
beforeRouteUpdate (to, from, next) {
this.userIdentifier = to.params.userIdentifier;
next();
},
data () {
return {
userIdentifier: '',
@@ -70,10 +78,6 @@ export default {
computed: {
...mapState({ user: 'user.data' }),
},
beforeRouteUpdate (to, from, next) {
this.userIdentifier = to.params.userIdentifier;
next();
},
watch: {
userIdentifier () {
this.isSearching = true;

View File

@@ -17,6 +17,7 @@
<li
v-for="item in achievements"
:key="item.path"
v-b-tooltip.hover="item.notes"
>
<form @submit.prevent="saveItem(item)">
<span
@@ -27,7 +28,7 @@
{{ item.value }}
</span>
:
{{ item.text || item.key }}
{{ item.text || item.key }} - <i> {{ item.key }} </i>
</span>
<div
@@ -68,6 +69,7 @@
<li
v-for="item in nestedAchievements[achievementType]"
:key="item.path"
v-b-tooltip.hover="item.notes"
>
<form @submit.prevent="saveItem(item)">
<span
@@ -78,7 +80,7 @@
{{ item.value }}
</span>
:
{{ item.text || item.key }}
{{ item.text || item.key }} - <i> {{ item.key }} </i>
</span>
<div
@@ -143,79 +145,28 @@ function getText (achievementItem) {
}
const { titleKey } = achievementItem;
if (titleKey !== undefined) {
return i18n.t(titleKey, 'en');
return i18n.t(titleKey);
}
const { singularTitleKey } = achievementItem;
if (singularTitleKey !== undefined) {
return i18n.t(singularTitleKey, 'en');
return i18n.t(singularTitleKey);
}
return achievementItem.key;
}
function collateItemData (self) {
const achievements = [];
const nestedAchievements = {};
const basePath = 'achievements';
const ownedAchievements = self.hero.achievements;
const allAchievements = content.achievements;
for (const key of Object.keys(ownedAchievements)) {
const value = ownedAchievements[key];
if (typeof value === 'object') {
nestedAchievements[key] = [];
for (const nestedKey of Object.keys(value)) {
const valueIsInteger = self.integerTypes.includes(key);
let text = nestedKey;
if (allAchievements[key] && allAchievements[key][nestedKey]) {
text = getText(allAchievements[key][nestedKey]);
function getNotes (achievementItem, count) {
if (achievementItem === undefined) {
return '';
}
nestedAchievements[key].push({
key: nestedKey,
text,
achievementType: key,
modified: false,
path: `${basePath}.${key}.${nestedKey}`,
value: value[nestedKey],
valueIsInteger,
});
const { textKey } = achievementItem;
if (textKey !== undefined) {
return i18n.t(textKey, { count });
}
} else {
const valueIsInteger = self.integerTypes.includes(key);
achievements.push({
key,
text: getText(allAchievements[key]),
modified: false,
path: `${basePath}.${key}`,
value: ownedAchievements[key],
valueIsInteger,
});
const { singularTextKey } = achievementItem;
if (singularTextKey !== undefined) {
return i18n.t(singularTextKey, { count });
}
}
for (const key of Object.keys(allAchievements)) {
if (key !== '' && !key.endsWith('UltimateGear') && !key.endsWith('Quest')) {
if (ownedAchievements[key] === undefined) {
const valueIsInteger = self.integerTypes.includes(key);
achievements.push({
key,
text: getText(allAchievements[key]),
modified: false,
path: `${basePath}.${key}`,
value: valueIsInteger ? 0 : false,
valueIsInteger,
neverOwned: true,
});
}
}
}
self.achievements = achievements;
self.nestedAchievements = nestedAchievements;
}
function resetData (self) {
collateItemData(self);
self.nestedAchievementKeys.forEach(itemType => { self.expandItemType[itemType] = false; });
return '';
}
export default {
@@ -241,26 +192,34 @@ export default {
},
nestedAchievementKeys: ['quests', 'ultimateGearSets'],
integerTypes: ['streak', 'perfect', 'birthday', 'habiticaDays', 'habitSurveys', 'habitBirthdays',
'valentine', 'congrats', 'shinySeed', 'goodluck', 'thankyou', 'seafoam', 'snowball', 'quests'],
'valentine', 'congrats', 'shinySeed', 'goodluck', 'thankyou', 'seafoam', 'snowball', 'quests',
'rebirths', 'rebirthLevel', 'greeting', 'spookySparkles', 'nye', 'costumeContests', 'congrats',
'getwell', 'beastMasterCount', 'mountMasterCount', 'triadBingoCount',
],
cardTypes: ['greeting', 'birthday', 'valentine', 'goodluck', 'thankyou', 'greeting', 'nye',
'congrats', 'getwell'],
achievements: [],
nestedAchievements: {},
};
},
watch: {
resetCounter () {
resetData(this);
this.resetData();
},
},
mounted () {
resetData(this);
this.resetData();
},
methods: {
async saveItem (item) {
// prepare the item's new value and path for being saved
this.hero.achievementPath = item.path;
this.hero.achievementVal = item.value;
await this.saveHero({ hero: this.hero, msg: item.path });
await this.saveHero({
hero: {
_id: this.hero._id,
achievementPath: item.path,
achievementVal: item.value,
},
msg: item.path,
});
item.modified = false;
},
enableValueChange (item) {
@@ -270,6 +229,85 @@ export default {
item.value = !item.value;
}
},
resetData () {
this.collateItemData();
this.nestedAchievementKeys.forEach(itemType => { this.expandItemType[itemType] = false; });
},
collateItemData () {
const achievements = [];
const nestedAchievements = {};
const basePath = 'achievements';
const ownedAchievements = this.hero.achievements;
const allAchievements = content.achievements;
const ownedKeys = Object.keys(ownedAchievements).sort();
for (const key of ownedKeys) {
const value = ownedAchievements[key];
let contentKey = key;
if (this.cardTypes.indexOf(key) !== -1) {
contentKey += 'Cards';
}
if (typeof value === 'object') {
nestedAchievements[key] = [];
for (const nestedKey of Object.keys(value)) {
const valueIsInteger = this.integerTypes.includes(key);
let text = nestedKey;
if (allAchievements[key] && allAchievements[key][contentKey]) {
text = getText(allAchievements[key][contentKey]);
}
let notes = '';
if (allAchievements[key] && allAchievements[key][contentKey]) {
notes = getNotes(allAchievements[key][contentKey], ownedAchievements[key]);
}
nestedAchievements[key].push({
key: nestedKey,
text,
notes,
achievementType: key,
modified: false,
path: `${basePath}.${key}.${nestedKey}`,
value: value[nestedKey],
valueIsInteger,
});
}
} else {
const valueIsInteger = this.integerTypes.includes(key);
achievements.push({
key,
text: getText(allAchievements[contentKey]),
notes: getNotes(allAchievements[contentKey], ownedAchievements[key]),
modified: false,
path: `${basePath}.${key}`,
value: ownedAchievements[key],
valueIsInteger,
});
}
}
const allKeys = Object.keys(allAchievements).sort();
for (const key of allKeys) {
if (key !== '' && !key.endsWith('UltimateGear') && !key.endsWith('Quest')) {
const ownedKey = key.replace('Cards', '');
if (ownedAchievements[ownedKey] === undefined) {
const valueIsInteger = this.integerTypes.includes(ownedKey);
achievements.push({
key: ownedKey,
text: getText(allAchievements[key]),
notes: getNotes(allAchievements[key], 0),
modified: false,
path: `${basePath}.${ownedKey}`,
value: valueIsInteger ? 0 : false,
valueIsInteger,
neverOwned: true,
});
}
}
}
this.achievements = achievements;
this.nestedAchievements = nestedAchievements;
},
},
};
</script>

View File

@@ -1,5 +1,12 @@
<template>
<form @submit.prevent="saveHero({ hero, msg: 'Contributor details', clearData: true })">
<form
@submit.prevent="saveHero({ hero: {
_id: hero._id,
contributor: hero.contributor,
secret: hero.secret,
permissions: hero.permissions,
}, msg: 'Contributor details', clearData: true })"
>
<div class="card mt-2">
<div class="card-header">
<h3
@@ -8,6 +15,12 @@
@click="expand = !expand"
>
Contributor Details
<b
v-if="hasUnsavedChanges && !expand"
class="text-warning float-right"
>
Unsaved changes
</b>
</h3>
</div>
<div
@@ -104,13 +117,16 @@
</div>
<div
v-if="expand"
class="card-footer"
class="card-footer d-flex align-items-center justify-content-between"
>
<input
type="submit"
value="Save"
class="btn btn-primary mt-1"
>
<b v-if="hasUnsavedChanges" class="text-warning float-right">
Unsaved changes
</b>
</div>
</div>
</form>
@@ -190,6 +206,10 @@ export default {
type: Object,
required: true,
},
hasUnsavedChanges: {
type: Boolean,
required: true,
},
},
data () {
return {

View File

@@ -1,5 +1,11 @@
<template>
<form @submit.prevent="saveHero({ hero, msg: 'Authentication' })">
<form
@submit.prevent="saveHero({ hero: {
_id: hero._id,
auth: hero.auth,
preferences: hero.preferences,
}, msg: 'Authentication' })"
>
<div class="card mt-2">
<div class="card-header">
<h3
@@ -38,7 +44,10 @@
<strong v-else>No</strong>
</div>
</div>
<div v-if="cronError" class="form-group row">
<div
v-if="cronError"
class="form-group row"
>
<label class="col-sm-3 col-form-label">lastCron value:</label>
<strong>{{ hero.lastCron | formatDate }}</strong>
<br>
@@ -53,12 +62,12 @@
<div class="col-sm-9 col-form-label">
<strong>
{{ hero.auth.timestamps.loggedin | formatDate }}</strong>
<button
<a
class="btn btn-warning btn-sm ml-4"
@click="resetCron()"
>
Reset Cron to Yesterday
</button>
</a>
</div>
</div>
<div class="form-group row">
@@ -110,13 +119,14 @@
<div class="form-group row">
<label class="col-sm-3 col-form-label">API Token</label>
<div class="col-sm-9">
<button
<a
href="#"
value="Change API Token"
class="btn btn-danger"
@click="changeApiToken()"
>
Change API Token
</button>
</a>
<div
v-if="tokenModified"
>
@@ -268,13 +278,24 @@ export default {
return false;
},
async changeApiToken () {
this.hero.changeApiToken = true;
await this.saveHero({ hero: this.hero, msg: 'API Token' });
await this.saveHero({
hero: {
_id: this.hero._id,
changeApiToken: true,
},
msg: 'API Token',
});
this.tokenModified = true;
},
resetCron () {
this.hero.resetCron = true;
this.saveHero({ hero: this.hero, msg: 'Last Cron', clearData: true });
this.saveHero({
hero: {
_id: this.hero._id,
resetCron: true,
},
msg: 'Last Cron',
clearData: true,
});
},
},
};

View File

@@ -46,7 +46,7 @@
:
<span :class="{ ownedItem: !item.neverOwned }">{{ item.text }}</span>
</span>
{{ item.set }}
- {{ itemType }}.{{item.key}} - <i> {{ item.set }}</i>
<div
v-if="item.modified"
@@ -232,11 +232,14 @@ export default {
},
methods: {
async saveItem (item) {
// prepare the item's new value and path for being saved
this.hero.purchasedPath = item.path;
this.hero.purchasedVal = item.value;
await this.saveHero({ hero: this.hero, msg: item.path });
await this.saveHero({
hero: {
_id: this.hero._id,
purchasedPath: item.path,
purchasedVal: item.value,
},
msg: item.path,
});
item.modified = false;
},
enableValueChange (item) {

View File

@@ -15,10 +15,17 @@
<privileges-and-gems
:hero="hero"
:reset-counter="resetCounter"
:has-unsaved-changes="hasUnsavedChanges([hero.flags, unModifiedHero.flags],
[hero.auth, unModifiedHero.auth],
[hero.balance, unModifiedHero.balance],
[hero.secret, unModifiedHero.secret])"
/>
<subscription-and-perks
:hero="hero"
:group-plans="groupPlans"
:has-unsaved-changes="hasUnsavedChanges([hero.purchased.plan,
unModifiedHero.purchased.plan])"
/>
<cron-and-auth
@@ -29,6 +36,7 @@
<user-profile
:hero="hero"
:reset-counter="resetCounter"
:has-unsaved-changes="hasUnsavedChanges([hero.profile, unModifiedHero.profile])"
/>
<party-and-quest
@@ -47,6 +55,12 @@
:preferences="hero.preferences"
/>
<stats
:hero="hero"
:has-unsaved-changes="hasUnsavedChanges([hero.stats, unModifiedHero.stats])"
:reset-counter="resetCounter"
/>
<items-owned
:hero="hero"
:reset-counter="resetCounter"
@@ -67,8 +81,18 @@
:reset-counter="resetCounter"
/>
<user-history
:hero="hero"
:reset-counter="resetCounter"
/>
<contributor-details
:hero="hero"
:hasUnsavedChanges="hasUnsavedChanges(
[hero.contributor, unModifiedHero.contributor],
[hero.permissions, unModifiedHero.permissions],
[hero.secret, unModifiedHero.secret],
)"
:reset-counter="resetCounter"
@clear-data="clearData"
/>
@@ -109,6 +133,7 @@
</style>
<script>
import isEqualWith from 'lodash/isEqualWith';
import BasicDetails from './basicDetails';
import ItemsOwned from './itemsOwned';
import CronAndAuth from './cronAndAuth';
@@ -121,6 +146,8 @@ import Transactions from './transactions';
import SubscriptionAndPerks from './subscriptionAndPerks';
import CustomizationsOwned from './customizationsOwned.vue';
import Achievements from './achievements.vue';
import UserHistory from './userHistory.vue';
import Stats from './stats.vue';
import { userStateMixin } from '../../../mixins/userState';
@@ -135,6 +162,8 @@ export default {
PrivilegesAndGems,
ContributorDetails,
Transactions,
UserHistory,
Stats,
SubscriptionAndPerks,
UserProfile,
Achievements,
@@ -148,8 +177,10 @@ export default {
return {
userIdentifier: '',
resetCounter: 0,
unModifiedHero: {},
hero: {},
party: {},
groupPlans: [],
hasParty: false,
partyNotExistError: false,
adminHasPrivForParty: true,
@@ -168,6 +199,7 @@ export default {
},
methods: {
clearData () {
this.unModifiedHero = {};
this.hero = {};
},
@@ -176,6 +208,7 @@ export default {
this.$emit('changeUserIdentifier', id); // change user identifier in Admin Panel's form
this.hero = await this.$store.dispatch('hall:getHero', { uuid: id });
this.unModifiedHero = JSON.parse(JSON.stringify(this.hero));
if (!this.hero.flags) {
this.hero.flags = {
@@ -206,8 +239,38 @@ export default {
}
}
if (this.hero.purchased.plan.planId === 'group_plan_auto') {
try {
this.groupPlans = await this.$store.dispatch('hall:getHeroGroupPlans', { heroId: this.hero._id });
} catch (e) {
this.groupPlans = [];
}
}
this.resetCounter += 1; // tell child components to reinstantiate from scratch
},
hasUnsavedChanges (...comparisons) {
for (const index in comparisons) {
if (index && comparisons[index]) {
const objs = comparisons[index];
const obj1 = objs[0];
const obj2 = objs[1];
if (!isEqualWith(obj1, obj2, (x, y) => {
if (typeof x === 'object' && typeof y === 'object') {
return undefined;
}
if (x === false && y === undefined) {
// Special case for checkboxes
return true;
}
return x == y; // eslint-disable-line eqeqeq
})) {
return true;
}
}
}
return false;
},
},
};
</script>

View File

@@ -269,16 +269,19 @@ export default {
methods: {
async saveItem (item) {
// prepare the item's new value and path for being saved
this.hero.itemPath = item.path;
const toSave = {
_id: this.hero._id,
};
toSave.itemPath = item.path;
if (item.value === null) {
this.hero.itemVal = 'null';
toSave.itemVal = 'null';
} else if (item.value === false) {
this.hero.itemVal = 'false';
toSave.itemVal = 'false';
} else {
this.hero.itemVal = item.value;
toSave.itemVal = item.value;
}
await this.saveHero({ hero: this.hero, msg: item.key });
await this.saveHero({ hero: toSave, msg: item.key });
item.neverOwned = false;
item.modified = false;
},

View File

@@ -31,22 +31,41 @@
v-html="questErrors"
></p>
</div>
<div>
Party:
<span v-if="userHasParty">
yes: party ID {{ groupPartyData._id }},
member count {{ groupPartyData.memberCount }} (may be wrong)
<br>
<div v-if="userHasParty">
<div class="form-group row">
<label class="col-sm-3 col-form-label">
Party ID
</label>
<strong class="col-sm-9 col-form-label">
{{ groupPartyData._id }}
</strong>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">
Estimated Member Count
</label>
<strong class="col-sm-9 col-form-label">
{{ groupPartyData.memberCount }}
</strong>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">
Leader
</label>
<strong class="col-sm-9 col-form-label">
<span v-if="userIsPartyLeader">User is the party leader</span>
<span v-else>Party leader is
<router-link :to="{'name': 'userProfile', 'params': {'userId': groupPartyData.leader}}">
{{ groupPartyData.leader }}
</router-link>
</span>
</span>
<span v-else>no</span>
</strong>
</div>
<div
class="btn btn-danger"
@click="removeFromParty()">Remove from Party</div>
</div>
<strong v-else>User is not in a party.</strong>
<div class="subsection-start">
<p v-html="questStatus"></p>
</div>
@@ -56,6 +75,7 @@
<script>
import * as quests from '@/../../common/script/content/quests';
import saveHero from '../mixins/saveHero';
function determineQuestStatus (self) {
// Quest data is in the user doc and party doc. They can be out of sync.
@@ -271,6 +291,7 @@ function resetData (self) {
}
export default {
mixins: [saveHero],
props: {
resetCounter: {
type: Number,
@@ -318,5 +339,14 @@ export default {
mounted () {
resetData(this);
},
methods: {
removeFromParty () {
this.saveHero({
hero: { _id: this.userId, removeFromParty: true },
msg: 'Removed from party',
reloadData: true,
});
},
},
};
</script>

View File

@@ -1,5 +1,11 @@
<template>
<form @submit.prevent="saveHero({hero, msg: 'Privileges or Gems or Moderation Notes'})">
<form @submit.prevent="saveHero({hero: {
_id: hero._id,
flags: hero.flags,
balance: hero.balance,
auth: hero.auth,
secret: hero.secret,
}, msg: 'Privileges or Gems or Moderation Notes'})">
<div class="card mt-2">
<div class="card-header">
<h3
@@ -8,6 +14,9 @@
@click="expand = !expand"
>
Privileges, Gem Balance
<b v-if="hasUnsavedChanges && !expand" class="text-warning float-right">
Unsaved changes
</b>
</h3>
</div>
<div
@@ -117,13 +126,16 @@
</div>
<div
v-if="expand"
class="card-footer"
class="card-footer d-flex align-items-center justify-content-between"
>
<input
type="submit"
value="Save"
class="btn btn-primary mt-1"
>
<b v-if="hasUnsavedChanges" class="text-warning float-right">
Unsaved changes
</b>
</div>
</div>
</form>
@@ -169,6 +181,10 @@ export default {
type: Object,
required: true,
},
hasUnsavedChanges: {
type: Boolean,
required: true,
},
},
data () {
return {

View File

@@ -0,0 +1,72 @@
<template>
<div class="form-group row">
<label
class="col-sm-3 col-form-label"
:class="color"
>{{ label }}</label>
<div class="col-sm-9">
<input
:value="value"
class="form-control"
type="number"
:step="step"
:max="max"
:min="min"
@input="$emit('input', parseInt($event.target.value, 10))"
>
</div>
</div>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.about-row {
margin-left: 0px;
margin-right: 0px;
}
.red-label {
color: $red_100;
}
.blue-label {
color: $blue_100;
}
.purple-label {
color: $purple_300;
}
.yellow-label {
color: $yellow_50;
}
</style>
<script>
export default {
model: {
prop: 'value',
event: 'input',
},
props: {
label: {
type: String,
required: true,
},
color: {
type: String,
default: 'text-label',
},
value: {
type: Number,
required: true,
},
step: {
type: String,
default: 'any',
},
min: {
},
max: {
},
},
};
</script>

View File

@@ -0,0 +1,286 @@
<template>
<form @submit.prevent="submitClicked()">
<div class="card mt-2">
<div class="card-header">
<h3
class="mb-0 mt-0"
:class="{'open': expand}"
@click="expand = !expand"
>
Stats
<b v-if="hasUnsavedChanges && !expand" class="text-warning float-right">
Unsaved changes
</b>
</h3>
</div>
<div
v-if="expand"
class="card-body"
>
<stats-row
label="Health"
color="red-label"
:max="maxHealth"
v-model="hero.stats.hp" />
<stats-row
label="Experience"
color="yellow-label"
min="0"
:max="maxFieldHardCap"
v-model="hero.stats.exp" />
<stats-row
label="Mana"
color="blue-label"
min="0"
:max="maxFieldHardCap"
v-model="hero.stats.mp" />
<stats-row
label="Level"
step="1"
min="0"
:max="maxLevelHardCap"
v-model="hero.stats.lvl" />
<stats-row
label="Gold"
min="0"
:max="maxFieldHardCap"
v-model="hero.stats.gp" />
<div class="form-group row">
<label class="col-sm-3 col-form-label">Selected Class</label>
<div class="col-sm-9">
<select
id="selectedClass"
v-model="hero.stats.class"
class="form-control"
:disabled="hero.stats.lvl < 10"
>
<option value="warrior">Warrior</option>
<option value="wizard">Mage</option>
<option value="healer">Healer</option>
<option value="rogue">Rogue</option>
</select>
<small>
When changing class, players usually need stat points deallocated as well.
</small>
</div>
</div>
<h3>Stat Points</h3>
<stats-row
label="Unallocated"
min="0"
step="1"
:max="maxStatPoints"
v-model="hero.stats.points" />
<stats-row
label="Strength"
color="red-label"
min="0"
:max="maxStatPoints"
step="1"
v-model="hero.stats.str" />
<stats-row
label="Intelligence"
color="blue-label"
min="0"
:max="maxStatPoints"
step="1"
v-model="hero.stats.int" />
<stats-row
label="Perception"
color="purple-label"
min="0"
:max="maxStatPoints"
step="1"
v-model="hero.stats.per" />
<stats-row
label="Constitution"
color="yellow-label"
min="0"
:max="maxStatPoints"
step="1"
v-model="hero.stats.con" />
<div class="form-group row">
<div class="offset-sm-3 col-sm-9">
<button
type="button"
class="btn btn-warning btn-sm"
@click="deallocateStatPoints">
Deallocate all stat points
</button>
</div>
</div>
<div class="form-group row" v-if="statPointsIncorrect">
<div class="offset-sm-3 col-sm-9 text-danger">
Error: Sum of stat points should equal the users level
</div>
</div>
<h3>Buffs</h3>
<stats-row
label="Strength"
color="red-label"
min="0"
step="1"
v-model="hero.stats.buffs.str" />
<stats-row
label="Intelligence"
color="blue-label"
min="0"
step="1"
v-model="hero.stats.buffs.int" />
<stats-row
label="Perception"
color="purple-label"
min="0"
step="1"
v-model="hero.stats.buffs.per" />
<stats-row
label="Constitution"
color="yellow-label"
min="0"
step="1"
v-model="hero.stats.buffs.con" />
<div class="form-group row">
<div class="offset-sm-3 col-sm-9">
<button
type="button"
class="btn btn-warning btn-sm"
@click="resetBuffs">
Reset Buffs
</button>
</div>
</div>
</div>
<div
v-if="expand"
class="card-footer d-flex align-items-center justify-content-between"
>
<input
type="submit"
value="Save"
class="btn btn-primary mt-1"
>
<b v-if="hasUnsavedChanges" class="text-warning float-right">
Unsaved changes
</b>
</div>
</div>
</form>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.about-row {
margin-left: 0px;
margin-right: 0px;
}
</style>
<script>
import {
MAX_HEALTH,
MAX_STAT_POINTS,
MAX_LEVEL_HARD_CAP,
MAX_FIELD_HARD_CAP,
} from '@/../../common/script/constants';
import markdownDirective from '@/directives/markdown';
import saveHero from '../mixins/saveHero';
import { mapState } from '@/libs/store';
import { userStateMixin } from '../../../mixins/userState';
import StatsRow from './stats-row';
function resetData (self) {
self.expand = false;
}
export default {
directives: {
markdown: markdownDirective,
},
components: {
StatsRow,
},
mixins: [
userStateMixin,
saveHero,
],
computed: {
...mapState({ user: 'user.data' }),
statPointsIncorrect () {
if (this.hero.stats.lvl >= 10) {
return (parseInt(this.hero.stats.points, 10)
+ parseInt(this.hero.stats.str, 10)
+ parseInt(this.hero.stats.int, 10)
+ parseInt(this.hero.stats.per, 10)
+ parseInt(this.hero.stats.con, 10)
) !== this.hero.stats.lvl;
}
return false;
},
},
props: {
resetCounter: {
type: Number,
required: true,
},
hero: {
type: Object,
required: true,
},
hasUnsavedChanges: {
type: Boolean,
required: true,
},
},
data () {
return {
expand: false,
maxHealth: MAX_HEALTH,
maxStatPoints: MAX_STAT_POINTS,
maxLevelHardCap: MAX_LEVEL_HARD_CAP,
maxFieldHardCap: MAX_FIELD_HARD_CAP,
};
},
watch: {
resetCounter () {
resetData(this);
},
},
mounted () {
resetData(this);
},
methods: {
submitClicked () {
if (this.statPointsIncorrect) {
return;
}
this.saveHero({
hero: {
_id: this.hero._id,
stats: this.hero.stats,
},
msg: 'Stats',
});
},
resetBuffs () {
this.hero.stats.buffs = {
str: 0,
int: 0,
per: 0,
con: 0,
};
},
deallocateStatPoints () {
this.hero.stats.points = this.hero.stats.lvl;
this.hero.stats.str = 0;
this.hero.stats.int = 0;
this.hero.stats.per = 0;
this.hero.stats.con = 0;
},
},
};
</script>

View File

@@ -1,30 +1,135 @@
<template>
<form @submit.prevent="saveHero({ hero, msg: 'Subscription Perks' })">
<form
@submit.prevent="saveHero({ hero: {
_id: hero._id,
purchased: hero.purchased
}, msg: 'Subscription Perks' })"
>
<div class="card mt-2">
<div class="card-header">
<div class="card-header"
@click="expand = !expand">
<h3
class="mb-0 mt-0"
:class="{ 'open': expand }"
@click="expand = !expand"
>
Subscription, Monthly Perks
<b v-if="hasUnsavedChanges && !expand" class="text-warning float-right">
Unsaved changes
</b>
</h3>
</div>
<div
v-if="expand"
class="card-body"
>
<div v-if="hero.purchased.plan.paymentMethod">
<div
class="form-group row"
>
<label class="col-sm-3 col-form-label">
Payment method:
<strong>{{ hero.purchased.plan.paymentMethod }}</strong>
</label>
<div class="col-sm-9">
<input v-model="hero.purchased.plan.paymentMethod"
class="form-control"
type="text"
v-if="!isRegularPaymentMethod"
>
<select
v-else
v-model="hero.purchased.plan.paymentMethod"
class="form-control"
type="text"
>
<option value="groupPlan">Group Plan</option>
<option value="Stripe">Stripe</option>
<option value="Apple">Apple</option>
<option value="Google">Google</option>
<option value="Amazon Payments">Amazon</option>
<option value="PayPal">PayPal</option>
<option value="Gift">Gift</option>
<option value="">Clear out</option>
</select>
</div>
</div>
<div
class="form-group row"
>
<label class="col-sm-3 col-form-label">
Payment schedule:
</label>
<div class="col-sm-9">
<input v-model="hero.purchased.plan.planId"
class="form-control"
type="text"
v-if="!isRegularPlanId"
>
<select
v-else
v-model="hero.purchased.plan.planId"
class="form-control"
type="text"
>
<option value="basic_earned">Monthly recurring</option>
<option value="basic_3mo">3 Months recurring</option>
<option value="basic_6mo">6 Months recurring</option>
<option value="basic_12mo">12 Months recurring</option>
<option value="group_monthly">Group Plan (legacy)</option>
<option value="group_plan_auto">Group Plan (auto)</option>
<option value="">Clear out</option>
</select>
</div>
</div>
<div
class="form-group row"
>
<label class="col-sm-3 col-form-label">
Customer ID:
</label>
<div class="col-sm-9">
<input
v-model="hero.purchased.plan.customerId"
class="form-control"
type="text"
>
</div>
</div>
<div class="form-group row"
v-if="hero.purchased.plan.planId === 'group_plan_auto'">
<label class="col-sm-3 col-form-label">
Group Plan Memberships:
</label>
<div class="col-sm-9 col-form-label">
<loading-spinner
v-if="!groupPlans"
dark-color=true
/>
<b
v-else-if="groupPlans.length === 0"
class="text-danger col-form-label"
>User is not part of an active group plan!</b>
<div
v-else
v-for="group in groupPlans"
:key="group._id"
class="card mb-2">
<div class="card-body">
<h6 class="card-title">{{ group.name }}
<small class="float-right">{{ group._id }}</small>
</h6>
<p class="card-text">
<strong>Leader: </strong>
<a
v-if="group.leader !== hero._id"
@click="switchUser(group.leader)"
>{{ group.leader }}</a>
<strong v-else class="text-success">This user</strong>
</p>
<p class="card-text">
<strong>Members: </strong> {{ group.memberCount }}
</p>
</div>
</div>
<div v-if="hero.purchased.plan.planId">
Payment schedule ("basic-earned" is monthly):
<strong>{{ hero.purchased.plan.planId }}</strong>
</div>
<div v-if="hero.purchased.plan.planId == 'group_plan_auto'">
Group plan ID:
<strong>{{ hero.purchased.plan.owner }}</strong>
</div>
<div
v-if="hero.purchased.plan.dateCreated"
@@ -85,8 +190,18 @@
<strong class="input-group-text">
{{ dateFormat(hero.purchased.plan.dateTerminated) }}
</strong>
<a class="btn btn-danger"
href="#"
v-b-modal.sub_termination_modal
v-if="!hero.purchased.plan.dateTerminated && hero.purchased.plan.planId">
Terminate
</a>
</div>
</div>
<small v-if="!hero.purchased.plan.dateTerminated
&& hero.purchased.plan.planId" class="text-success">
The subscription does not have a termination date and is active.
</small>
</div>
</div>
<div class="form-group row">
@@ -101,6 +216,35 @@
min="0"
step="1"
>
<small class="text-secondary">
Cumulative subscribed months across subscription periods.
</small>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">
Extra months:
</label>
<div class="col-sm-9">
<div class="input-group">
<input
v-model="hero.purchased.plan.extraMonths"
class="form-control"
type="number"
min="0"
step="any"
>
<div class="input-group-append">
<a class="btn btn-warning"
@click="applyExtraMonths"
v-if="hero.purchased.plan.dateTerminated && hero.purchased.plan.extraMonths > 0">
Apply Credit
</a>
</div>
</div>
<small class="text-secondary">
Additional credit that is applied if a subscription is cancelled.
</small>
</div>
</div>
<div class="form-group row">
@@ -174,10 +318,6 @@
>
</div>
</div>
<div v-if="hero.purchased.plan.extraMonths > 0">
Additional credit (applied upon cancellation):
<strong>{{ hero.purchased.plan.extraMonths }}</strong>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label">
Mystery Items:
@@ -199,18 +339,64 @@
</span>
</div>
</div>
<div class="form-group row"
v-if="!isConvertingToGroupPlan && hero.purchased.plan.planId !== 'group_plan_auto'">
<div class="offset-sm-3 col-sm-9">
<button
type="button"
class="btn btn-secondary btn-sm"
@click="beginGroupPlanConvert">
Begin converting to group plan subscription
</button>
</div>
</div>
<div class="form-group row"
v-if="isConvertingToGroupPlan">
<label class="col-sm-3 col-form-label">
Group Plan group ID:
</label>
<div class="col-sm-9">
<input
v-model="groupPlanID"
class="form-control"
type="text"
>
</div>
</div>
</div>
<div
v-if="expand"
class="card-footer"
class="card-footer d-flex align-items-center justify-content-between"
>
<input
type="submit"
value="Save"
class="btn btn-primary mt-1"
@click="saveClicked"
>
<b v-if="hasUnsavedChanges" class="text-warning float-right">
Unsaved changes
</b>
</div>
</div>
<b-modal id="sub_termination_modal" title="Set Termination Date">
<p>
You can set the sub benefit termination date to today or to the last
day of the current billing cycle. Any extra subscription credit will
then be processed and automatically added onto the selected date.
</p>
<template #modal-footer>
<div class="mt-3 btn btn-secondary" @click="$bvModal.hide('sub_termination_modal')">
Close
</div>
<div class="mt-3 btn btn-danger" @click="terminateSubscription()">
Set to Today
</div>
<div class="mt-3 btn btn-danger" @click="terminateSubscription(todayWithRemainingCycle)">
Set to {{ todayWithRemainingCycle.utc().format('MM/DD/YYYY') }}
</div>
</template>
</b-modal>
</form>
</template>
@@ -231,21 +417,38 @@
</style>
<script>
import isUUID from 'validator/es/lib/isUUID';
import moment from 'moment';
import { getPlanContext } from '@/../../common/script/cron';
import saveHero from '../mixins/saveHero';
import subscriptionBlocks from '../../../../../common/script/content/subscriptionBlocks';
import LoadingSpinner from '@/components/ui/loadingSpinner';
export default {
mixins: [saveHero],
components: {
LoadingSpinner,
},
props: {
hero: {
type: Object,
required: true,
},
hasUnsavedChanges: {
type: Boolean,
required: true,
},
groupPlans: {
type: Array,
default: null,
},
},
data () {
return {
expand: false,
isConvertingToGroupPlan: false,
groupPlanID: '',
subscriptionBlocks,
};
},
computed: {
@@ -255,6 +458,30 @@ export default {
if (!currentPlanContext.nextHourglassDate) return 'N/A';
return currentPlanContext.nextHourglassDate.format('MMMM YYYY');
},
isRegularPlanId () {
return this.subscriptionBlocks[this.hero.purchased.plan.planId] !== undefined;
},
isRegularPaymentMethod () {
return [
'groupPlan',
'Group Plan',
'Stripe',
'Apple',
'Google',
'Amazon Payments',
'PayPal',
'Gift',
].includes(this.hero.purchased.plan.paymentMethod);
},
todayWithRemainingCycle () {
const now = moment();
const monthCount = subscriptionBlocks[this.hero.purchased.plan.planId].months;
const terminationDate = moment(this.hero.purchased.plan.dateCurrentTypeCreated || new Date());
while (terminationDate.isBefore(now)) {
terminationDate.add(monthCount, 'months');
}
return terminationDate;
},
},
methods: {
dateFormat (date) {
@@ -263,6 +490,46 @@ export default {
}
return moment(date).format('YYYY/MM/DD');
},
terminateSubscription (terminationDate) {
if (terminationDate) {
this.hero.purchased.plan.dateTerminated = terminationDate.utc().format();
} else {
this.hero.purchased.plan.dateTerminated = moment(new Date()).utc().format();
}
this.applyExtraMonths();
this.saveHero({ hero: this.hero, msg: 'Subscription Termination', reloadData: true });
},
applyExtraMonths () {
if (this.hero.purchased.plan.extraMonths > 0 || this.hero.purchased.plan.extraMonths !== '0') {
const date = moment(this.hero.purchased.plan.dateTerminated || new Date());
const extraMonths = Math.max(this.hero.purchased.plan.extraMonths, 0);
const extraDays = Math.ceil(30.5 * extraMonths);
this.hero.purchased.plan.dateTerminated = date.add(extraDays, 'days').utc().format();
this.hero.purchased.plan.extraMonths = 0;
}
},
beginGroupPlanConvert () {
this.isConvertingToGroupPlan = true;
this.hero.purchased.plan.owner = '';
},
saveClicked (e) {
e.preventDefault();
if (this.isConvertingToGroupPlan) {
if (!isUUID(this.groupPlanID)) {
alert('Invalid group ID');
return;
}
this.hero.purchased.plan.convertToGroupPlan = this.groupPlanID;
this.saveHero({ hero: this.hero, msg: 'Group Plan Subscription', reloadData: true });
} else {
this.saveHero({ hero: this.hero, msg: 'Subscription Perks', reloadData: true });
}
},
switchUser (id) {
if (window.confirm('Switch to this user?')) {
this.$emit('changeUserIdentifier', id);
}
},
},
};
</script>

View File

@@ -0,0 +1,263 @@
<template>
<div class="card mt-2">
<div class="card-header">
<h3
class="mb-0 mt-0"
:class="{'open': expand}"
@click="toggleHistoryOpen"
>
User History
</h3>
</div>
<div
v-if="expand"
class="card-body"
>
<div>
<div class="clearfix">
<div class="mb-4 float-left">
<button
class="page-header btn-flat tab-button textCondensed"
:class="{'active': selectedTab === 'armoire'}"
@click="selectTab('armoire')"
>
Armoire
</button>
<button
class="page-header btn-flat tab-button textCondensed"
:class="{'active': selectedTab === 'questInvites'}"
@click="selectTab('questInvites')"
>
Quest Invitations
</button>
<button
class="page-header btn-flat tab-button textCondensed"
:class="{'active': selectedTab === 'cron'}"
@click="selectTab('cron')"
>
Cron
</button>
</div>
</div>
<div class="row">
<div
v-if="selectedTab === 'armoire'"
class="col-12"
>
<table class="table">
<tr>
<th
v-once
>
{{ $t('timestamp') }}
</th>
<th v-once>
Client
</th>
<th
v-once
>
Received
</th>
</tr>
<tr
v-for="entry in armoire"
:key="entry.timestamp"
>
<td>
<span
v-b-tooltip.hover="entry.timestamp"
>{{ entry.timestamp | timeAgo }}</span>
</td>
<td>{{ entry.client }}</td>
<td>{{ entry.reward }}</td>
</tr>
</table>
</div>
<div
v-if="selectedTab === 'questInvites'"
class="col-12"
>
<table class="table">
<tr>
<th
v-once
>
{{ $t('timestamp') }}
</th>
<th v-once>
Client
</th>
<th v-once>
Quest Key
</th>
<th v-once>
Response
</th>
</tr>
<tr
v-for="entry in questInviteResponses"
:key="entry.timestamp"
>
<td>
<span
v-b-tooltip.hover="entry.timestamp"
>{{ entry.timestamp | timeAgo }}</span>
</td>
<td>{{ entry.client }}</td>
<td>{{ entry.quest }}</td>
<td>{{ questInviteResponseText(entry.response) }}</td>
</tr>
</table>
</div>
<div
v-if="selectedTab === 'cron'"
class="col-12"
>
<table class="table">
<tr>
<th
v-once
>
{{ $t('timestamp') }}
</th>
<th v-once>
Client
</th>
<th v-once>
Checkin Count
</th>
</tr>
<tr
v-for="entry in cron"
:key="entry.timestamp"
>
<td>
<span
v-b-tooltip.hover="entry.timestamp"
>{{ entry.timestamp | timeAgo }}</span>
</td>
<td>{{ entry.client }}</td>
<td>{{ entry.checkinCount }}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.page-header.btn-flat {
background: transparent;
}
.tab-button {
height: 2rem;
font-size: 24px;
font-weight: bold;
font-stretch: condensed;
line-height: 1.33;
letter-spacing: normal;
color: $gray-10;
margin-right: 1.125rem;
padding-left: 0;
padding-right: 0;
padding-bottom: 2.5rem;
&.active, &:hover {
color: $purple-300;
box-shadow: 0px -0.25rem 0px $purple-300 inset;
outline: none;
}
}
</style>
<script>
import moment from 'moment';
import { userStateMixin } from '../../../mixins/userState';
export default {
filters: {
timeAgo (value) {
return moment(value).fromNow();
},
},
mixins: [userStateMixin],
props: {
hero: {
type: Object,
required: true,
},
resetCounter: {
type: Number,
required: true,
},
},
data () {
return {
expand: false,
selectedTab: 'armoire',
armoire: [],
questInviteResponses: [],
cron: [],
};
},
watch: {
resetCounter () {
if (this.expand) {
this.retrieveUserHistory();
}
},
},
methods: {
selectTab (type) {
this.selectedTab = type;
},
async toggleHistoryOpen () {
this.expand = !this.expand;
if (this.expand) {
this.retrieveUserHistory();
}
},
async retrieveUserHistory () {
const history = await this.$store.dispatch('adminPanel:getUserHistory', { userIdentifier: this.hero._id });
this.armoire = history.armoire;
this.questInviteResponses = history.questInviteResponses;
this.cron = history.cron;
},
questInviteResponseText (response) {
if (response === 'accept') {
return 'Accepted';
}
if (response === 'reject') {
return 'Rejected';
}
if (response === 'leave') {
return 'Left active quest';
}
if (response === 'invite') {
return 'Accepted as owner';
}
if (response === 'abort') {
return 'Aborted by owner';
}
if (response === 'abortByLeader') {
return 'Aborted by party leader';
}
if (response === 'cancel') {
return 'Cancelled before start';
}
if (response === 'cancelByLeader') {
return 'Cancelled before start by party leader';
}
return response;
},
},
};
</script>

View File

@@ -1,5 +1,10 @@
<template>
<form @submit.prevent="saveHero({hero, msg: 'Users Profile'})">
<form
@submit.prevent="saveHero({hero: {
_id: hero._id,
profile: hero.profile
}, msg: 'Users Profile'})"
>
<div class="card mt-2">
<div class="card-header">
<h3
@@ -8,6 +13,9 @@
@click="expand = !expand"
>
User Profile
<b v-if="hasUnsavedChanges && !expand" class="text-warning float-right">
Unsaved changes
</b>
</h3>
</div>
<div
@@ -51,13 +59,16 @@
</div>
<div
v-if="expand"
class="card-footer"
class="card-footer d-flex align-items-center justify-content-between"
>
<input
type="submit"
value="Save"
class="btn btn-primary mt-1"
>
<b v-if="hasUnsavedChanges" class="text-warning float-right">
Unsaved changes
</b>
</div>
</div>
</form>
@@ -101,6 +112,10 @@ export default {
type: Object,
required: true,
},
hasUnsavedChanges: {
type: Boolean,
required: true,
},
},
data () {
return {

View File

@@ -37,9 +37,9 @@
<h3>{{ $t('footerCompany') }}</h3>
<ul>
<li>
<router-link to="/static/contact">
<a href="mailto:admin@habitica.com">
{{ $t('contactUs') }}
</router-link>
</a>
</li>
<li>
<router-link to="/static/press-kit">
@@ -55,9 +55,9 @@
</li>
<li>
<a
href="https://habitica.fandom.com/wiki/Whats_New"
target="_blank"
>{{ $t('oldNews') }}
@click="showBailey()"
>
{{ $t('oldNews') }}
</a>
</li>
</ul>
@@ -80,7 +80,7 @@
</li>
<li>
<a
href="https://habitica.fandom.com/wiki/Contributing_to_Habitica"
href="https://github.com/HabitRPG/habitica/wiki/Contributing-to-Habitica"
target="_blank"
>{{ $t('companyContribute') }}
</a>
@@ -131,13 +131,6 @@
>{{ $t('requestFeature') }}
</a>
</li>
<li>
<a
href="https://habitica.fandom.com/"
target="_blank"
>{{ $t('wiki') }}
</a>
</li>
</ul>
</div>
<!-- Developers -->
@@ -165,13 +158,6 @@
>{{ $t('guidanceForBlacksmiths') }}
</a>
</li>
<li>
<a
href="https://habitica.fandom.com/wiki/Extensions,_Add-Ons,_and_Customizations"
target="_blank"
>{{ $t('communityExtensions') }}
</a>
</li>
</ul>
</div>
@@ -212,12 +198,12 @@
</a>
<a
class="social-circle"
href="https://twitter.com/habitica/"
href="https://bsky.app/profile/habitica.com"
target="_blank"
>
<div
class="social-icon svg-icon twitter"
v-html="icons.twitter"
class="social-icon svg-icon bluesky"
v-html="icons.bluesky"
></div>
</a>
<a
@@ -525,7 +511,7 @@ footer {
background-color: $gray-500;
color: $gray-50;
padding: 32px 142px 40px;
a {
a, a:not([href]) {
color: $gray-50;
}
a:hover {
@@ -814,7 +800,7 @@ h3 {
}
}
.twitter svg {
.bluesky svg {
background-color: #e1e0e3;
fill: #878190;
height: 24px;
@@ -853,7 +839,7 @@ import Vue from 'vue';
// images
import melior from '@/assets/svg/melior.svg';
import twitter from '@/assets/svg/twitter.svg';
import bluesky from '@/assets/svg/bluesky.svg';
import facebook from '@/assets/svg/facebook.svg';
import instagram from '@/assets/svg/instagram.svg';
import tumblr from '@/assets/svg/tumblr.svg';
@@ -885,7 +871,7 @@ export default {
return {
icons: Object.freeze({
melior,
twitter,
bluesky,
facebook,
instagram,
tumblr,
@@ -1003,7 +989,6 @@ export default {
async bossRage () {
await axios.post('/api/v4/debug/boss-rage');
},
async makeAdmin () {
await axios.post('/api/v4/debug/make-admin');
// @TODO: Notification.text('You are now an admin!
@@ -1013,6 +998,9 @@ export default {
donate () {
this.$root.$emit('bv::show::modal', 'buy-gems', { alreadyTracked: true });
},
showBailey () {
this.$root.$emit('bv::show::modal', 'new-stuff');
},
},
};
</script>

View File

@@ -3,7 +3,7 @@
v-if="member.preferences"
class="avatar"
:style="{width, height, paddingTop}"
:class="backgroundClass"
:class="topLevelClassList"
@click.prevent="castEnd()"
>
<div
@@ -55,7 +55,11 @@
<span :class="[getGearClass('eyewear'), specialMountClass]"></span>
<span :class="[getGearClass('head'), specialMountClass]"></span>
<span :class="[getGearClass('headAccessory'), specialMountClass]"></span>
<span :class="['hair_flower_' + member.preferences.hair.flower, specialMountClass]"></span>
<span
:class="[
'hair_flower_' + member.preferences.hair.flower, specialMountClass
]"
></span>
<span
v-if="!hideGear('shield')"
:class="[getGearClass('shield'), specialMountClass]"
@@ -63,6 +67,7 @@
<span
v-if="!hideGear('weapon')"
:class="[getGearClass('weapon'), specialMountClass]"
class="weapon"
></span>
</template>
<!-- Resting-->
@@ -96,15 +101,23 @@
.avatar {
width: 141px;
height: 147px;
image-rendering: pixelated;
position: relative;
cursor: pointer;
&.centered-avatar {
margin: 0 auto;
}
// resetting the additional padding
margin-bottom: -0.5rem !important;
}
.character-sprites {
width: 90px;
height: 90px;
display: inline-flex;
}
.character-sprites span {
@@ -123,22 +136,49 @@
.invert {
filter: invert(100%);
}
.debug {
border: 1px solid red;
.character-sprites {
border: 1px solid blue;
}
.weapon {
border: 1px solid green;
}
span {
border: 1px solid yellow;
}
}
</style>
<script>
import some from 'lodash/some';
import moment from 'moment';
import { mapState } from '@/libs/store';
import foolPet from '../mixins/foolPet';
import ClassBadge from '@/components/members/classBadge';
/**
* TODO replace avatarOnly with multiple options like
* - showMount
* - showPet
* - showBackground
* - showWeapons
*/
export default {
components: {
ClassBadge,
},
mixins: [foolPet],
props: {
debugMode: {
type: Boolean,
default: false,
},
member: {
type: Object,
required: true,
@@ -156,14 +196,21 @@ export default {
},
overrideAvatarGear: {
type: Object,
default (data) {
return data;
},
},
width: {
type: Number,
default: 140,
type: String,
default: '141px',
},
height: {
type: Number,
default: 147,
type: String,
default: '147px',
},
centerAvatar: {
type: Boolean,
default: false,
},
spritesMargin: {
type: String,
@@ -171,11 +218,16 @@ export default {
},
overrideTopPadding: {
type: String,
default: null,
},
showVisualBuffs: {
type: Boolean,
default: true,
},
showWeapon: {
type: Boolean,
default: true,
},
},
computed: {
...mapState({
@@ -204,6 +256,19 @@ export default {
return val;
},
topLevelClassList () {
const classes = [this.backgroundClass];
if (this.debugMode) {
classes.push('debug');
}
if (this.centerAvatar) {
classes.push('centered-avatar');
}
return classes.join(' ');
},
backgroundClass () {
if (this.member) {
const { background } = this.member.preferences;
@@ -256,11 +321,10 @@ export default {
return null;
},
petClass () {
if (some(
this.currentEventList,
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'Fungi',
)) {
return this.foolPet(this.member.items.currentPet);
const foolEvent = this.currentEventList?.find(event => moment()
.isBetween(event.start, event.end) && event.aprilFools);
if (foolEvent) {
return this.foolPet(this.member.items.currentPet, foolEvent.aprilFools);
}
if (this.member?.items.currentPet) return `Pet-${this.member.items.currentPet}`;
return '';
@@ -290,6 +354,10 @@ export default {
},
hideGear (gearType) {
if (!this.member) return true;
if (!this.showWeapon) {
return true;
}
if (gearType === 'weapon') {
const equippedWeapon = this.member.items.gear[this.costumeClass][gearType];

View File

@@ -1,352 +0,0 @@
<template>
<div>
<div
v-if="isUserMentioned"
class="mentioned-icon"
></div>
<div
v-if="hasPermission(user, 'moderator') && msg.flagCount"
class="message-hidden"
>
{{ flagCountDescription }}
</div>
<div class="card-body">
<user-link
:user-id="msg.uuid"
:name="msg.user"
:backer="msg.backer"
:contributor="msg.contributor"
/>
<p class="time">
<span
v-if="msg.username"
class="mr-1"
>@{{ msg.username }}</span>
<span
v-if="msg.username"
class="mr-1"
></span>
<span
v-b-tooltip.hover="messageDate"
>{{ msg.timestamp | timeAgo }}&nbsp;</span>
<span v-if="msg.client && user.contributor.level >= 4">({{ msg.client }})</span>
</p>
<div
ref="markdownContainer"
class="text markdown"
dir="auto"
v-html="parseMarkdown(msg.text)"
></div>
<hr>
<div
v-if="msg.id"
class="d-flex"
>
<div
class="action d-flex align-items-center"
@click="copyAsTodo(msg)"
>
<div
class="svg-icon"
v-html="icons.copy"
></div>
<div>{{ $t('copyAsTodo') }}</div>
</div>
<div
v-if="(user.flags.communityGuidelinesAccepted && msg.uuid !== 'system')
&& (!isMessageReported || hasPermission(user, 'moderator'))"
class="action d-flex align-items-center"
@click="report(msg)"
>
<div
v-once
class="svg-icon"
v-html="icons.report"
></div>
<div v-once>
{{ $t('report') }}
</div>
</div>
<div
v-if="msg.uuid === user._id || hasPermission(user, 'moderator')"
class="action d-flex align-items-center"
@click="remove()"
>
<div
v-once
class="svg-icon"
v-html="icons.delete"
></div>
<div v-once>
{{ $t('delete') }}
</div>
</div>
<div
v-b-tooltip="{title: likeTooltip(msg.likes[user._id])}"
class="ml-auto d-flex"
>
<div
v-if="likeCount > 0"
class="action d-flex align-items-center mr-0"
:class="{activeLike: msg.likes[user._id]}"
@click="like()"
>
<div
class="svg-icon"
:title="$t('liked')"
v-html="icons.liked"
></div>
+{{ likeCount }}
</div>
<div
v-if="likeCount === 0"
class="action d-flex align-items-center mr-0"
:class="{activeLike: msg.likes[user._id]}"
@click="like()"
>
<div
class="svg-icon"
:title="$t('like')"
v-html="icons.like"
></div>
</div>
</div>
<span v-if="!msg.likes[user._id]">{{ $t('like') }}</span>
</div>
</div>
</div>
</template>
<style lang="scss">
.at-highlight {
background-color: rgba(213, 200, 255, 0.32);
padding: 0.1rem;
}
.at-text {
color: #6133b4;
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.mentioned-icon {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #bda8ff;
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
position: absolute;
right: -.5em;
top: -.5em;
}
.message-hidden {
margin-left: 1.5em;
margin-top: 1em;
color: red;
}
hr {
margin-bottom: 0.5rem;
margin-top: 0.5rem;
}
.card-body {
padding: 0.75rem 1.25rem 0.75rem 1.25rem;
.time {
font-size: 12px;
color: #878190;
margin-bottom: 0.5rem;
}
.text {
font-size: 14px;
color: #4e4a57;
text-align: initial;
min-height: 0rem;
}
}
.action {
display: inline-block;
color: #878190;
margin-right: 1em;
font-size: 12px;
:hover {
cursor: pointer;
}
.svg-icon {
color: #A5A1AC;
margin-right: .2em;
width: 16px;
}
}
.activeLike {
color: $purple-300;
.svg-icon {
color: $purple-400;
}
}
</style>
<script>
import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep';
import escapeRegExp from 'lodash/escapeRegExp';
import { CHAT_FLAG_LIMIT_FOR_HIDING, CHAT_FLAG_FROM_SHADOW_MUTE } from '@/../../common/script/constants';
import renderWithMentions from '@/libs/renderWithMentions';
import { userStateMixin } from '../../mixins/userState';
import userLink from '../userLink';
import deleteIcon from '@/assets/svg/delete.svg';
import copyIcon from '@/assets/svg/copy.svg';
import likeIcon from '@/assets/svg/like.svg';
import likedIcon from '@/assets/svg/liked.svg';
import reportIcon from '@/assets/svg/report.svg';
export default {
components: { userLink },
filters: {
timeAgo (value) {
return moment(value).fromNow();
},
date (value) {
// @TODO: Vue doesn't support this so we cant user preference
return moment(value).toDate().toString();
},
},
mixins: [userStateMixin],
props: {
msg: {},
groupId: {},
},
data () {
return {
icons: Object.freeze({
like: likeIcon,
copy: copyIcon,
report: reportIcon,
delete: deleteIcon,
liked: likedIcon,
}),
reported: false,
};
},
computed: {
isUserMentioned () {
const message = this.msg;
if (message.highlight) return true;
const { user } = this;
const displayName = user.profile.name;
const { username } = user.auth.local;
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
message.highlight = new RegExp(pattern, 'i').test(message.text);
return message.highlight;
},
likeCount () {
const message = this.msg;
if (!message.likes) return 0;
let likeCount = 0;
for (const key of Object.keys(message.likes)) {
const like = message.likes[key];
if (like) likeCount += 1;
}
return likeCount;
},
isMessageReported () {
return (this.msg.flags && this.msg.flags[this.user.id]) || this.reported;
},
flagCountDescription () {
if (!this.msg.flagCount) return '';
if (this.msg.flagCount < CHAT_FLAG_LIMIT_FOR_HIDING) return 'Message flagged once, not hidden';
if (this.msg.flagCount < CHAT_FLAG_FROM_SHADOW_MUTE) return 'Message hidden';
return 'Message hidden (shadow-muted)';
},
messageDate () {
const date = moment(this.msg.timestamp).toDate();
return date.toString();
},
},
mounted () {
const links = this.$refs.markdownContainer.getElementsByTagName('a');
for (let i = 0; i < links.length; i += 1) {
let link = links[i].pathname;
// Internet Explorer does not provide the leading slash character in the pathname
link = link.charAt(0) === '/' ? link : `/${link}`;
if (link.startsWith('/profile/')) {
links[i].onclick = ev => {
ev.preventDefault();
this.$router.push({ path: link });
};
}
}
this.CHAT_FLAG_LIMIT_FOR_HIDING = CHAT_FLAG_LIMIT_FOR_HIDING;
this.CHAT_FLAG_FROM_SHADOW_MUTE = CHAT_FLAG_FROM_SHADOW_MUTE;
this.$emit('chat-card-mounted', this.msg.id);
},
methods: {
async like () {
const message = cloneDeep(this.msg);
await this.$store.dispatch('chat:like', {
groupId: this.groupId,
chatId: message.id,
});
message.likes[this.user._id] = !message.likes[this.user._id];
this.$emit('message-liked', message);
this.$root.$emit('bv::hide::tooltip');
},
likeTooltip (likedStatus) {
if (!likedStatus) return this.$t('like');
return null;
},
copyAsTodo (message) {
this.$root.$emit('habitica::copy-as-todo', message);
},
report () {
this.$root.$on('habitica:report-result', data => {
if (data.ok) {
this.reported = true;
}
this.$root.$off('habitica:report-result');
});
this.$root.$emit('habitica::report-chat', {
message: this.msg,
groupId: this.groupId || 'privateMessage',
});
},
async remove () {
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) return; // eslint-disable-line no-alert
const message = this.msg;
this.$emit('message-removed', message);
await this.$store.dispatch('chat:deleteChat', {
groupId: this.groupId,
chatId: message.id,
});
},
parseMarkdown (text) {
return renderWithMentions(text, this.user);
},
},
};
</script>

View File

@@ -3,15 +3,6 @@
ref="container"
class="container-fluid"
>
<div class="row">
<div class="col-12">
<copy-as-todo-modal
:group-type="groupType"
:group-name="groupName"
:group-id="groupId"
/>
</div>
</div>
<div class="row loadmore">
<div v-if="canLoadMore">
<div class="loadmore-divider"></div>
@@ -33,11 +24,14 @@
<div
v-for="msg in messages.filter(m => chat && canViewFlag(m))"
:key="msg.id"
class="message-row"
:class="{ 'margin-right': user._id !== msg.uuid}"
>
<div class="d-flex">
<avatar
v-if="user._id !== msg.uuid && msg.uuid !== 'system'"
class="avatar-left"
:height="null"
:class="{ invisible: avatarUnavailable(msg) }"
:member="msg.userStyles || cachedProfileData[msg.uuid] || {}"
:avatar-only="true"
@@ -45,20 +39,19 @@
:override-top-padding="'14px'"
@click.native="showMemberModal(msg.uuid)"
/>
<div class="card">
<chat-card
<message-card
:msg="msg"
:group-id="groupId"
:user-sent-message="user._id === msg.uuid"
@message-liked="messageLiked"
@message-removed="messageRemoved"
@show-member-modal="showMemberModal"
@chat-card-mounted="itemWasMounted"
@message-card-mounted="itemWasMounted"
/>
</div>
<avatar
v-if="user._id === msg.uuid"
:class="{ invisible: avatarUnavailable(msg) }"
:member="msg.userStyles || cachedProfileData[msg.uuid] || {}"
:height="null"
:avatar-only="true"
:hide-class-badge="true"
:override-top-padding="'14px'"
@@ -105,11 +98,6 @@
}
}
.avatar-left {
margin-left: -1.5rem;
margin-right: 2rem;
}
.hr {
width: 100%;
height: 20px;
@@ -137,11 +125,27 @@
margin-bottom: .5em;
padding: 0rem;
width: 90%;
&.system-message {
width: 100%;
}
}
.message-scroll .d-flex {
min-width: 1px;
}
.message-row {
margin-left: 12px;
margin-right: 0;
margin-bottom: 1.2rem;
&:not(.margin-right) {
.d-flex {
justify-content: flex-end;
}
}
}
</style>
<script>
@@ -152,13 +156,13 @@ import findIndex from 'lodash/findIndex';
import { userStateMixin } from '../../mixins/userState';
import Avatar from '../avatar';
import copyAsTodoModal from './copyAsTodoModal';
import chatCard from './chatCard';
import MessageCard from '@/components/messages/messageCard.vue';
// TODO merge chatMessages.vue (party message list) with messageList.vue (private message list)
export default {
components: {
copyAsTodoModal,
chatCard,
MessageCard,
Avatar,
},
mixins: [userStateMixin],

View File

@@ -1,105 +0,0 @@
<template>
<b-modal
id="copyAsTodo"
:title="$t('copyMessageAsToDo')"
:hide-footer="true"
size="md"
>
<div class="form-group">
<input
v-model="task.text"
class="form-control"
type="text"
>
</div>
<div class="form-group">
<textarea
v-model="task.notes"
class="form-control"
rows="5"
focus-element="true"
></textarea>
</div>
<hr>
<task
v-if="task._id"
:is-user="isUser"
:task="task"
/>
<div class="modal-footer">
<button
class="btn btn-secondary"
@click="close()"
>
{{ $t('close') }}
</button>
<button
class="btn btn-primary"
@click="saveTodo()"
>
{{ $t('submit') }}
</button>
</div>
</b-modal>
</template>
<script>
import taskDefaults from '@/../../common/script/libs/taskDefaults';
import { mapActions } from '@/libs/store';
import markdownDirective from '@/directives/markdown';
import notificationsMixin from '@/mixins/notifications';
import Task from '@/components/tasks/task';
const baseUrl = 'https://habitica.com';
export default {
directives: {
markdown: markdownDirective,
},
components: {
Task,
},
mixins: [notificationsMixin],
props: ['copyingMessage', 'groupType', 'groupName', 'groupId'],
data () {
return {
isUser: true,
task: {},
};
},
mounted () {
this.$root.$on('habitica::copy-as-todo', message => {
const notes = `${message.user || 'system message'}${message.user ? ' wrote' : ''} in [${this.groupName}](${this.groupPath()})`;
const newTask = {
text: message.text,
type: 'todo',
notes,
};
this.task = taskDefaults(newTask, this.$store.state.user.data);
this.$root.$emit('bv::show::modal', 'copyAsTodo');
});
},
beforeDestroy () {
this.$root.$off('habitica::copy-as-todo');
},
methods: {
...mapActions({
createTask: 'tasks:create',
}),
groupPath () {
if (this.groupType === 'party') {
return `${baseUrl}/party`;
}
return `${baseUrl}/groups/guild/${this.groupId}`;
},
close () {
this.$root.$emit('bv::hide::modal', 'copyAsTodo');
},
saveTodo () {
this.createTask(this.task);
this.text(this.$t('messageAddedAsToDo'));
this.$root.$emit('bv::hide::modal', 'copyAsTodo');
},
},
};
</script>

View File

@@ -22,13 +22,13 @@
:placeholder="placeholder"
:class="{'user-entry': newMessage}"
:maxlength="MAX_MESSAGE_LENGTH"
@keydown="updateCarretPosition"
@keydown="autoCompleteMixinUpdateCarretPosition"
@keyup.ctrl.enter="sendMessageShortcut()"
@keydown.tab="handleTab($event)"
@keydown.up="selectPreviousAutocomplete($event)"
@keydown.down="selectNextAutocomplete($event)"
@keypress.enter="selectAutocomplete($event)"
@keydown.esc="handleEscape($event)"
@keydown.tab="autoCompleteMixinHandleTab($event)"
@keydown.up="autoCompleteMixinSelectPreviousAutocomplete($event)"
@keydown.down="autoCompleteMixinSelectNextAutocomplete($event)"
@keypress.enter="autoCompleteMixinSelectAutocomplete($event)"
@keydown.esc="autoCompleteMixinHandleEscape($event)"
@paste="disableMessageSendShortcut()"
></textarea>
<span>{{ currentLength }} / {{ MAX_MESSAGE_LENGTH }}</span>
@@ -36,8 +36,8 @@
ref="autocomplete"
:text="newMessage"
:textbox="textbox"
:coords="coords"
:caret-position="caretPosition"
:coords="mixinData.autoComplete.coords"
:caret-position="mixinData.autoComplete.caretPosition"
:chat="group.chat"
@select="selectedAutocomplete"
/>
@@ -74,7 +74,7 @@
<slot name="additionRow"></slot>
<div class="row">
<div class="hr col-12"></div>
<chat-message
<chat-messages
:chat.sync="group.chat"
:group-type="group.type"
:group-id="group._id"
@@ -86,16 +86,15 @@
</template>
<script>
import debounce from 'lodash/debounce';
import { MAX_MESSAGE_LENGTH } from '@/../../common/script/constants';
import externalLinks from '../../mixins/externalLinks';
import autocomplete from '../chat/autoComplete';
import communityGuidelines from './communityGuidelines';
import chatMessage from '../chat/chatMessages';
import chatMessages from '../chat/chatMessages';
import { mapState } from '@/libs/store';
import markdownDirective from '@/directives/markdown';
import { autoCompleteHelperMixin } from '@/mixins/autoCompleteHelper';
export default {
directives: {
@@ -104,23 +103,18 @@ export default {
components: {
autocomplete,
communityGuidelines,
chatMessage,
chatMessages,
},
mixins: [externalLinks],
mixins: [externalLinks, autoCompleteHelperMixin],
props: ['label', 'group', 'placeholder'],
data () {
return {
newMessage: '',
sending: false,
caretPosition: 0,
chat: {
submitDisable: false,
submitTimeout: null,
},
coords: {
TOP: 0,
LEFT: 0,
},
textbox: null,
MAX_MESSAGE_LENGTH: MAX_MESSAGE_LENGTH.toString(),
};
@@ -142,35 +136,6 @@ export default {
this.handleExternalLinks();
},
methods: {
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
getCoord (e, text) {
this.caretPosition = text.selectionEnd;
const div = document.createElement('div');
const span = document.createElement('span');
const copyStyle = getComputedStyle(text);
[].forEach.call(copyStyle, prop => {
div.style[prop] = copyStyle[prop];
});
div.style.position = 'absolute';
document.body.appendChild(div);
div.textContent = text.value.substr(0, this.caretPosition);
span.textContent = text.value.substr(this.caretPosition) || '.';
div.appendChild(span);
this.coords = {
TOP: span.offsetTop,
LEFT: span.offsetLeft,
};
document.body.removeChild(div);
},
updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
_updateCarretPosition (eventUpdate) {
const text = eventUpdate.target;
this.getCoord(eventUpdate, text);
},
async sendMessageShortcut () {
// If the user recently pasted in the text field, don't submit
if (!this.chat.submitDisable) {
@@ -221,50 +186,6 @@ export default {
}, 500);
},
handleTab (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
if (e.shiftKey) {
this.$refs.autocomplete.selectPrevious();
} else {
this.$refs.autocomplete.selectNext();
}
}
},
handleEscape (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.cancel();
}
},
selectNextAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectNext();
}
},
selectPreviousAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectPrevious();
}
},
selectAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
if (this.$refs.autocomplete.selected !== null) {
e.preventDefault();
this.$refs.autocomplete.makeSelection();
} else {
// no autocomplete selected, newline instead
this.$refs.autocomplete.cancel();
}
}
},
selectedAutocomplete (newText, newCaret) {
this.newMessage = newText;
// Wait for v-modal to update
@@ -273,7 +194,6 @@ export default {
this.textbox.focus();
});
},
fetchRecentMessages () {
this.$emit('fetchRecentMessages');
},
@@ -284,10 +204,7 @@ export default {
beforeRouteUpdate (to, from, next) {
// Reset chat
this.newMessage = '';
this.coords = {
TOP: 0,
LEFT: 0,
};
this.autoCompleteMixinResetCoordsPosition();
next();
},

View File

@@ -225,10 +225,9 @@
</a>
</div>
<div class="quest-icon">
<div
<Sprite
class="quest"
:class="`inventory_quest_scroll_${questData.key}`"
></div>
:image-name="`inventory_quest_scroll_${questData.key}`" />
</div>
</div>
<div

View File

@@ -297,7 +297,7 @@
<div class="topbar-dropdown">
<router-link
v-if="user.permissions.fullAccess ||
user.permissions.userSupport || user.permissions.newsPoster"
user.permissions.userSupport"
class="topbar-dropdown-item dropdown-item"
:to="{name: 'adminPanel'}"
>
@@ -334,11 +334,6 @@
href="https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link"
target="_blank"
>{{ $t('requestFeature') }}</a>
<a
class="topbar-dropdown-item dropdown-item"
href="https://habitica.fandom.com/wiki/Habitica_Wiki"
target="_blank"
>{{ $t('wiki') }}</a>
</div>
</li>
</b-navbar-nav>

View File

@@ -12,20 +12,21 @@
<strong> {{ notification.data.title }} </strong>
<span> {{ notification.data.text }} </span>
</div>
<div
<Sprite
slot="icon"
class="mt-3"
:class="notification.data.icon"
></div>
:image-name="notification.data.icon" />
</base-notification>
</template>
<script>
import BaseNotification from './base';
import Sprite from '@/components/ui/sprite.vue';
export default {
components: {
BaseNotification,
Sprite,
},
props: {
notification: {
@@ -41,7 +42,8 @@ export default {
},
methods: {
action () {
if (!this.notification || !this.notification.data) {
if (!this.notification || !this.notification.data
|| this.notification.data.destination === this.$route.path) {
return;
}
if (this.notification.data.destination.indexOf('backgrounds') !== -1) {

View File

@@ -10,20 +10,21 @@
slot="content"
v-html="$t('newSubscriberItem')"
></div>
<div
<Sprite
slot="icon"
:class="mysteryClass"
></div>
:image-name="mysteryClass" />
</base-notification>
</template>
<script>
import moment from 'moment';
import BaseNotification from './base';
import Sprite from '@/components/ui/sprite.vue';
export default {
components: {
BaseNotification,
Sprite,
},
props: ['notification', 'canRemove'],
computed: {

View File

@@ -106,7 +106,7 @@
</div>
<div slot="drawer-header">
<div class="drawer-tab-container">
<div class="clearfix">
<div class="clearfix mb-2">
<toggle-switch
class="float-right align-with-tab"
:label="$t(costumeMode ? 'useCostume' : 'autoEquipBattleGear')"

View File

@@ -114,7 +114,6 @@
</style>
<script>
import some from 'lodash/some';
import moment from 'moment';
import { v4 as uuid } from 'uuid';
import { mapState } from '@/libs/store';
@@ -183,13 +182,12 @@ export default {
return 'GreyedOut';
},
imageName () {
if (this.isOwned() && some(
this.currentEventList,
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'Fungi',
)) {
if (this.isSpecial()) return `stable_${this.foolPet(this.item.key)}`;
const foolEvent = this.currentEventList?.find(event => moment()
.isBetween(event.start, event.end) && event.aprilFools);
if (this.isOwned() && foolEvent) {
if (this.isSpecial()) return `stable_${this.foolPet(this.item.key, foolEvent.aprilFools)}`;
const petString = `${this.item.eggKey}-${this.item.key}`;
return `stable_${this.foolPet(petString)}`;
return `stable_${this.foolPet(petString, foolEvent.aprilFools)}`;
}
if (this.isOwned() || (this.mountOwned() && this.isHatchable())) {

View File

@@ -28,7 +28,6 @@
:name="member.profile.name"
:backer="member.backer"
:contributor="member.contributor"
:smaller-style="true"
/>
<inline-class-badge
v-if="member.stats"

View File

@@ -0,0 +1,114 @@
<template>
<div
class="d-inline-flex like-button"
@click="like()"
>
<div
v-b-tooltip="{title: likeTooltip(likeCount)}"
class="d-flex"
>
<div
v-if="likeCount > 0"
class="action d-flex align-items-center mr-0"
:class="{isLiked: true, currentUserLiked: likedByCurrentUser}"
>
<div
class="svg-icon mr-1"
:title="$t('liked')"
v-html="likedIcon"
></div>
+{{ likeCount }}
</div>
<div
v-if="likeCount === 0"
class="action d-flex align-items-center mr-1"
>
<div
class="svg-icon"
:title="$t('like')"
v-html="icons.like"
></div>
</div>
</div>
<span v-if="likeCount === 0">{{ $t('like') }}</span>
</div>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/tiers.scss';
.action {
display: inline-block;
margin-right: 1em;
.svg-icon {
color: $gray-100;
width: 16px;
}
&.isLiked.currentUserLiked {
color: $purple-200;
font-weight: bold;
.svg-icon {
color: $purple-300;
}
}
}
.like-button {
color: $gray-100;
font-size: 12px;
line-height: 16px;
&:hover {
cursor: pointer;
color: $purple-200;
.svg-icon {
color: $purple-300;
}
}
}
</style>
<script>
import likeIcon from '@/assets/svg/like.svg';
import likedIcon from '@/assets/svg/liked.svg';
export default {
props: {
likeCount: {
type: Number,
},
likedByCurrentUser: {
type: Boolean,
},
},
data () {
return {
icons: Object.freeze({
like: likeIcon,
liked: likedIcon,
}),
};
},
computed: {
likedIcon () {
return this.likedByCurrentUser ? this.icons.liked : this.icons.like;
},
},
methods: {
async like () {
this.$emit('toggle-like');
},
likeTooltip (likedStatus) {
if (!likedStatus) return this.$t('like');
return null;
},
},
};
</script>

View File

@@ -1,12 +1,44 @@
<template>
<div class="card-body">
<div
class="card"
:class="{
'system-message': isSystemMessage
}"
>
<div
v-b-tooltip.hover="messageDateForSystemMessage"
class="message-card"
:class="{
'user-sent-message': userSentMessage,
'user-received-message': !userSentMessage && !isSystemMessage,
'system-message': isSystemMessage
}"
>
<div
v-if="isUserMentioned"
class="mentioned-icon"
></div>
<div
v-if="userIsModerator && msg.flagCount"
class="message-hidden"
>
{{ flagCountDescription }}
</div>
<div
class="card-body"
>
<user-link
v-if="!isSystemMessage"
:user-id="msg.uuid"
:name="msg.user"
:backer="msg.backer"
:contributor="msg.contributor"
/>
<p class="time">
<p
v-if="!isSystemMessage"
class="time"
>
<span
v-if="msg.username"
class="mr-1"
@@ -14,12 +46,86 @@
v-if="msg.username"
class="mr-1"
></span>
<span
v-b-tooltip.hover="messageDate"
>{{ msg.timestamp | timeAgo }}&nbsp;</span>
<span v-if="msg.client && user.contributor.level >= 4"> ({{ msg.client }})</span>
<span v-b-tooltip.hover="messageDate">{{ msg.timestamp | timeAgo }}&nbsp;</span>
<span v-if="msg.client && user.contributor.level >= 4">
({{ msg.client }})
</span>
</p>
<b-dropdown
v-if="!isSystemMessage"
right="right"
variant="flat"
toggle-class="with-icon"
class="card-menu no-min-width"
:no-caret="true"
>
<template #button-content>
<span
v-once
class="svg-icon inline menuIcon color"
v-html="icons.menuIcon"
>
</span>
</template>
<b-dropdown-item
class="selectListItem"
@click="copy(msg)"
>
<span class="with-icon">
<span
v-once
class="svg-icon icon-16 color"
v-html="icons.copy"
></span>
<span v-once>
{{ $t('copy') }}
</span>
</span>
</b-dropdown-item>
<b-dropdown-item
v-if="canReportMessage"
class="selectListItem custom-hover--red"
@click="report(msg)"
>
<span class="with-icon">
<span
v-once
class="svg-icon icon-16 color"
v-html="icons.report"
></span>
<span v-once>
{{ $t('report') }}
</span>
</span>
</b-dropdown-item>
<b-dropdown-item
v-if="canDeleteMessage"
class="selectListItem custom-hover--red"
@click="remove()"
>
<span class="with-icon">
<span
v-once
class="svg-icon icon-16 color"
v-html="icons.delete"
></span>
<span v-once>
{{ $t('delete') }}
</span>
</span>
</b-dropdown-item>
</b-dropdown>
<div
v-if="isSystemMessage"
class="system-message-body"
>
{{ msg.unformattedText }}
</div>
<div
v-else
ref="markdownContainer"
class="text markdown"
dir="auto"
v-html="parseMarkdown(msg.text)"
@@ -31,43 +137,21 @@
<span v-once>{{ $t('reportedMessage') }}</span><br>
<span v-once>{{ $t('canDeleteNow') }}</span>
</div>
<hr>
<div
v-if="msg.id"
class="d-flex"
>
<div
v-if="!isMessageReported"
class="action d-flex align-items-center"
@click="report(msg)"
>
<div
v-once
class="svg-icon"
v-html="icons.report"
></div>
<div v-once>
{{ $t('report') }}
</div>
</div>
<div
class="action d-flex align-items-center"
@click="remove()"
>
<div
v-once
class="svg-icon"
v-html="icons.delete"
></div>
<div v-once>
{{ $t('delete') }}
</div>
<like-button
v-if="canLikeMessage"
class="mt-75"
:liked-by-current-user="msg.likes[user._id]"
:like-count="likeCount"
@toggle-like="like()"
/>
</div>
</div>
</div>
</template>
<style lang="scss">
.message-card {
.at-highlight {
background-color: rgba(213, 200, 255, 0.32);
padding: 0.1rem;
@@ -76,27 +160,50 @@
.at-text {
color: #6133b4;
}
.card-menu button {
justify-content: center;
margin: 0;
padding: 0;
height: 1rem;
width: 1rem;
}
.markdown p:last-of-type {
margin-bottom: 0;
}
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/tiers.scss';
.action {
display: inline-block;
color: $gray-200;
margin-right: 1em;
font-size: 12px;
:hover {
cursor: pointer;
.card {
background: transparent !important;
margin-bottom: 0 !important;
}
.svg-icon {
color: $gray-300;
margin-right: .2em;
.message-card:not(.system-message) {
background: white;
}
.mentioned-icon {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: $purple-500;
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
position: absolute;
right: -.5em;
top: -.5em;
}
.message-hidden {
margin-left: 1.5em;
margin-top: 1em;
color: red;
}
.active {
@@ -107,12 +214,22 @@
}
}
.message-card {
border-radius: 7px;
margin: 0;
padding: 1rem 0.75rem 0.5rem 1rem;
&.system-message {
padding-top: 0.5rem;
}
.card-body {
padding: 0.75rem 1.25rem 0.75rem 1.25rem;
position: relative;
padding: 0;
.time {
font-size: 12px;
color: $gray-200;
color: $gray-100;
margin-bottom: 0.5rem;
}
@@ -123,6 +240,23 @@
min-height: 0rem;
}
}
}
.card-menu {
position: absolute;
top: 0;
right: 0;
&:not(.show) {
display: none;
}
}
.card-body:hover {
.card-menu {
display: block;
}
}
hr {
margin-bottom: 0.5rem;
@@ -133,39 +267,146 @@
margin-top: 18px;
color: $red-50;
}
.selectListItem:not(:hover) .svg-icon.icon-16.color {
color: #{$gray-100}
}
.custom-hover--red {
--hover-color: #{$maroon-50};
--hover-background: #{rgba($red-500, 0.25)};
}
.user-sent-message {
border: 1px solid $purple-400;
}
.system-message {
border: 1px solid $purple-400;
}
.user-received-message {
border: 1px solid $gray-500;
}
.card-menu {
// icon-color is the menu icon itself
--icon-color: #{$gray-100};
--dropdown-item-hover-icon-color: #{$gray-100};
&:hover {
--icon-color: #{$purple-300};
}
}
.menuIcon {
width: 4px;
height: 1rem;
object-fit: contain;
}
.system-message-body {
line-height: 1.71;
text-align: center;
color: $purple-300;
}
</style>
<script>
import axios from 'axios';
import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep';
import escapeRegExp from 'lodash/escapeRegExp';
import { CHAT_FLAG_FROM_SHADOW_MUTE, CHAT_FLAG_LIMIT_FOR_HIDING } from '@/../../common/script/constants';
import externalLinks from '../../mixins/externalLinks';
import { CopyToClipboardMixin } from '@/mixins/copyToClipboard';
import renderWithMentions from '@/libs/renderWithMentions';
import { mapState } from '@/libs/store';
import userLink from '../userLink';
import deleteIcon from '@/assets/svg/delete.svg';
import reportIcon from '@/assets/svg/report.svg';
import menuIcon from '@/assets/svg/menu.svg';
import { userStateMixin } from '@/mixins/userState';
import copyIcon from '@/assets/svg/copy.svg';
import LikeButton from '@/components/messages/likeButton.vue';
const LikeLogicMixin = {
computed: {
likeCount () {
const message = this.msg;
if (!message.likes) return 0;
let likeCount = 0;
for (const key of Object.keys(message.likes)) {
const like = message.likes[key];
if (like) likeCount += 1;
}
return likeCount;
},
},
methods: {
async like () {
const message = cloneDeep(this.msg);
await this.$store.dispatch('chat:like', {
groupId: this.groupId,
chatMessageId: this.privateMessageMode ? message.uniqueMessageId : message.id,
});
message.likes[this.user._id] = !message.likes[this.user._id];
this.$emit('message-liked', message);
this.$root.$emit('bv::hide::tooltip');
},
},
};
export default {
components: {
LikeButton,
userLink,
},
filters: {
timeAgo (value) {
return moment(value).fromNow();
},
date (value) {
// @TODO: Vue doesn't support this so we cant user preference
return moment(value).toDate().toString();
},
mixins: [externalLinks],
},
mixins: [
externalLinks, userStateMixin, LikeLogicMixin,
CopyToClipboardMixin,
],
props: {
msg: {},
msg: {
type: Object,
},
groupId: {
type: String,
},
privateMessageMode: {
type: Boolean,
},
userSentMessage: {
type: Boolean,
},
},
data () {
return {
icons: Object.freeze({
delete: deleteIcon,
report: reportIcon,
copy: copyIcon,
menuIcon,
}),
reported: false,
};
@@ -175,19 +416,100 @@ export default {
isMessageReported () {
return (this.msg.flags && this.msg.flags[this.user.id]) || this.reported;
},
messageDateForSystemMessage () {
return this.isSystemMessage ? this.messageDate : '';
},
messageDate () {
const date = moment(this.msg.timestamp).toDate();
return date.toString();
},
userIsModerator () {
return this.hasPermission(this.user, 'moderator');
},
isSystemMessage () {
return this.msg.uuid === 'system';
},
canLikeMessage () {
if (this.isSystemMessage) {
return false;
}
if (this.privateMessageMode) {
return Boolean(this.msg.uniqueMessageId);
}
return this.msg.id;
},
canDeleteMessage () {
return this.privateMessageMode
|| this.msg.uuid === this.user._id
|| this.userIsModerator;
},
canReportMessage () {
if (this.privateMessageMode) {
return !this.isMessageReported;
}
return (this.user.flags.communityGuidelinesAccepted && this.msg.uuid !== 'system')
&& (!this.isMessageReported || this.userIsModerator);
},
isUserMentioned () {
const message = this.msg;
if (message.highlight) {
return true;
}
const { user } = this;
const displayName = user.profile.name;
const { username } = user.auth.local;
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
message.highlight = new RegExp(pattern, 'i').test(message.text);
return message.highlight;
},
flagCountDescription () {
if (!this.msg.flagCount) {
return '';
}
if (this.msg.flagCount < CHAT_FLAG_LIMIT_FOR_HIDING) {
return 'Message flagged once, not hidden';
}
if (this.msg.flagCount < CHAT_FLAG_FROM_SHADOW_MUTE) {
return 'Message hidden';
}
return 'Message hidden (shadow-muted)';
},
},
mounted () {
this.$emit('message-card-mounted');
this.handleExternalLinks();
this.mapProfileLinksToModal();
},
updated () {
this.handleExternalLinks();
this.mapProfileLinksToModal();
},
methods: {
mapProfileLinksToModal () {
const links = this.$refs.markdownContainer.getElementsByTagName('a');
for (let i = 0; i < links.length; i += 1) {
let link = links[i].pathname;
// Internet Explorer does not provide the leading slash character in the pathname
link = link.charAt(0) === '/' ? link : `/${link}`;
if (link.startsWith('/profile/')) {
links[i].onclick = ev => {
ev.preventDefault();
this.$router.push({ path: link });
};
}
}
},
report () {
this.$root.$on('habitica:report-result', data => {
if (data.ok) {
@@ -199,16 +521,29 @@ export default {
this.$root.$emit('habitica::report-chat', {
message: this.msg,
groupId: 'privateMessage',
groupId: this.groupId,
});
},
async remove () {
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) return; // eslint-disable-line no-alert
// eslint-disable-next-line no-alert
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) {
return;
}
const message = this.msg;
this.$emit('message-removed', message);
if (this.privateMessageMode) {
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
} else {
await this.$store.dispatch('chat:deleteChat', {
groupId: this.groupId,
chatId: message.id,
});
}
},
copy (message) {
this.mixinCopyToClipboard(message.text, this.$t('messageCopiedToClipboard'));
},
parseMarkdown (text) {
return renderWithMentions(text, this.user);

View File

@@ -1,9 +1,9 @@
<template>
<div
ref="container"
class="container-fluid"
class="message-list"
>
<div class="row loadmore">
<div class="loadmore">
<div v-if="canLoadMore && !isLoading">
<div class="loadmore-divider-holder">
<div class="loadmore-divider"></div>
@@ -28,7 +28,7 @@
<div
v-for="(msg) in messages"
:key="msg.id"
class="row message-row"
class="message-row"
:class="{ 'margin-right': user._id !== msg.uuid}"
>
<div
@@ -39,28 +39,33 @@
class="avatar-left"
:member="conversationOpponentUser"
:avatar-only="true"
:override-top-padding="'14px'"
:show-weapon="true"
:debug-mode="false"
:height="null"
:override-top-padding="'0'"
:hide-class-badge="true"
@click.native="showMemberModal(msg.uuid)"
/>
<div
class="card"
:class="{'card-right': user._id !== msg.uuid, 'card-left': user._id === msg.uuid}"
>
<message-card
:msg="msg"
:user-sent-message="user._id === msg.uuid"
:group-id="'privateMessage'"
:private-message-mode="true"
@message-liked="messageLiked"
@message-removed="messageRemoved"
@show-member-modal="showMemberModal"
@message-card-mounted="itemWasMounted"
/>
</div>
<avatar
v-if="user && user._id === msg.uuid"
class="avatar-right"
:member="user"
:height="null"
:avatar-only="true"
:show-weapon="true"
:debug-mode="false"
:hide-class-badge="true"
:override-top-padding="'14px'"
:override-top-padding="'0'"
@click.native="showMemberModal(msg.uuid)"
/>
</div>
@@ -71,18 +76,18 @@
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.avatar {
width: 170px;
min-width: 8rem;
height: 120px;
padding-top: 0 !important;
}
.avatar-right {
margin-left: -1rem;
.avatar-left, .avatar-right {
align-self: center;
::v-deep .character-sprites {
margin-right: 1rem !important;
margin-bottom: -5px !important;
padding-bottom: 0 !important;
margin-top: -1px !important;
}
::v-deep .avatar {
margin-left: -1.75rem;
margin-right: -0.5rem;
}
}
@@ -91,10 +96,19 @@
margin-bottom: 1rem;
padding: 0rem;
width: 684px;
}
.message-list {
width: 100%;
padding-right: 10px;
margin-right: 0 !important;
}
.message-row {
margin-left: 12px;
margin-right: 12px;
margin-right: 0;
margin-bottom: 1.2rem;
&:not(.margin-right) {
.d-flex {
@@ -102,26 +116,6 @@
}
}
}
@media only screen and (max-width: 1200px) {
.card {
width: 100%;
}
}
@media only screen and (min-width: 1400px) {
.message-row {
margin-left: -15px;
margin-right: -30px;
}
}
.card-left {
border: 1px solid $purple-500;
}
.card-right {
border: 1px solid $gray-500;
}
.hr {
width: 100%;
@@ -280,6 +274,9 @@ export default {
// container.style.overflowY = 'scroll';
}
}, 50),
messageLiked (message) {
this.$emit('message-liked', message);
},
messageRemoved (message) {
this.$emit('message-removed', message);
},

View File

@@ -692,7 +692,7 @@
<div class="form-inline clearfix">
<Sprite
class="pull-left"
:class="'inventory_quest_scroll_' + item.key"
:image-name="'inventory_quest_scroll_' + item.key"
style="margin-right: 10px"
/>
<p>{{ item.text() }}</p>

View File

@@ -107,7 +107,7 @@ export default {
if (lastPublishedPost) this.posts.push(lastPublishedPost);
// If the user is authorized, show any draft
if (this.user && this.user.contributor.newsPoster) {
if (this.user && (this.user.permissions.news || this.user.permissions.fullAccess)) {
this.posts.unshift(
...postsFromServer
.filter(p => !p.published || moment().isBefore(p.publishDate)),

View File

@@ -843,7 +843,6 @@ export default {
purchasedPlanIdInfo () {
if (!this.subscriptionBlocks[this.user.purchased.plan.planId]) {
// @TODO: find which subs are in the common
// console.log(this.subscriptionBlocks
// [this.user.purchased.plan.planId]); // eslint-disable-line
return {
price: 0,

View File

@@ -27,27 +27,15 @@
@changedPosition="tabSelected($event)"
>
<div slot="right-item">
<div
<a
v-once
id="petLikeToEatMarket"
class="drawer-help-text"
href="/static/faq#pet-foods"
target="_blank"
>
<span>{{ $t('petLikeToEat') + ' ' }}</span>
<span
class="svg-icon inline icon-16"
v-html="icons.information"
></span>
</div>
<b-popover
target="petLikeToEatMarket"
:placement="'top'"
>
<div
v-once
class="popover-content-text"
v-html="$t('petLikeToEatText')"
></div>
</b-popover>
<span>{{ $t('petLikeToEat') }}</span>
</a>
</div>
</drawer-header-tabs>
</div>
@@ -80,7 +68,6 @@
import _filter from 'lodash/filter';
import { mapState } from '@/libs/store';
import inventoryUtils from '@/mixins/inventoryUtils';
import svgInformation from '@/assets/svg/information.svg';
import Drawer from '@/components/ui/drawer';
import DrawerSlider from '@/components/ui/drawerSlider';
@@ -127,10 +114,6 @@ export default {
},
],
selectedDrawerTab: this.defaultSelectedTab,
icons: Object.freeze({
information: svgInformation,
}),
};
},
computed: {

View File

@@ -3,6 +3,7 @@
<div
v-for="currency of currencies"
:key="currency.key"
:needed-currency-only="neededCurrencyOnly"
class="d-flex align-items-center"
>
<div
@@ -54,6 +55,9 @@ export default {
amountNeeded: {
type: Number,
},
neededCurrencyOnly: {
type: Boolean,
},
},
data () {
return {
@@ -66,34 +70,34 @@ export default {
},
computed: {
currencies () {
const currencies = [];
currencies.push({
const currencies = [{
type: 'hourglasses',
icon: this.icons.hourglasses,
value: this.userHourglasses,
});
},
currencies.push({
{
type: 'gems',
icon: this.icons.gem,
value: this.userGems,
});
},
currencies.push({
{
type: 'gold',
icon: this.icons.gold,
value: this.userGold,
});
}];
for (const currency of currencies) {
if (
currency.type === this.currencyNeeded
if (currency.type === this.currencyNeeded
&& !this.enoughCurrency(this.currencyNeeded, this.amountNeeded)
) {
currency.notEnough = true;
}
}
if (this.neededCurrencyOnly) {
return currencies.filter(curr => curr.type === this.currencyNeeded);
}
return currencies;
},
},

View File

@@ -31,13 +31,6 @@
&colon;&nbsp;
<a href="mailto:admin@habitica.com">admin&commat;habitica&period;com</a>
<br>
{{ $t('generalQuestionsSite') }}
&colon;&nbsp;
<a
target="_blank"
@click.prevent="openBugReportModal(true)"
> {{ $t('askQuestion') }}</a>
<br>
{{ $t('businessInquiries') }}
&colon;&nbsp;
<a href="mailto:admin@habitica.com">admin@habitica.com</a>
@@ -54,10 +47,8 @@
<script>
import { mapState } from '@/libs/store';
import { goToModForm } from '@/libs/modform';
import reportBug from '@/mixins/reportBug.js';
export default {
mixins: [reportBug],
computed: {
...mapState({
user: 'user.data',

View File

@@ -66,16 +66,13 @@
class="nav-link"
>{{ $t('presskit') }}</a>
</router-link>
<router-link
class="nav-item"
tag="li"
to="/static/contact"
>
<li class="nav-item">
<a
v-once
class="nav-link"
href="mailto:admin@habitica.com"
>{{ $t('contactUs') }}</a>
</router-link>
</li>
</ul>
<ul
v-else

View File

@@ -135,7 +135,7 @@
}
}
.twitter svg {
.bluesky svg {
background-color: $purple-50;
fill: $purple-500;
&:hover {

View File

@@ -86,7 +86,7 @@
>
<a
target="_blank"
href="https://habitica.fandom.com/wiki/Markdown_Cheat_Sheet"
href="https://github.com/HabitRPG/habitica/wiki/Markdown-in-Habitica"
:class="cssClass('headings')"
>{{ $t('markdownHelpLink') }}</a>
</small>

View File

@@ -129,6 +129,12 @@
padding-top: 6px;
padding-left: 24px;
padding-right: 24px;
a {
line-height: 1.33;
color: $gray-500;
font-weight: normal;
}
}
.drawer-tab {

View File

@@ -20,6 +20,7 @@
}"
>
<input
ref="textInput"
:value="value"
class="form-control"
:type="inputType"
@@ -29,12 +30,15 @@
}"
:readonly="readonly"
:aria-readonly="readonly"
autocomplete="off"
:placeholder="placeholder"
@keyup="handleChange"
@keyup.enter="$emit('enter')"
@blur="$emit('blur')"
>
</div>
<template v-if="!hideErrorLine">
<div
v-for="issue in invalidIssues"
:key="issue"
@@ -42,6 +46,7 @@
>
{{ issue }} &nbsp;
</div>
</template>
</div>
</div>
</template>
@@ -85,6 +90,10 @@ export default {
type: Array,
default: () => [],
},
hideErrorLine: {
type: Boolean,
default: false,
},
},
data () {
return {
@@ -107,6 +116,9 @@ export default {
this.wasChanged = true;
this.$emit('update:value', value);
},
focus () {
this.$refs.textInput.focus();
},
},
};
</script>
@@ -128,4 +140,12 @@ export default {
margin-bottom: 0;
}
/* this removes safari "save username" UI, we only search for one, we dont want to save it */
input::-webkit-contacts-auto-fill-button,
input::-webkit-credentials-auto-fill-button {
visibility: hidden;
position: absolute;
right: 0;
}
</style>

View File

@@ -29,20 +29,12 @@
@import '~@/assets/scss/colors.scss';
.user-link { // this is the user name
font-family: 'Roboto Condensed', sans-serif;
font-weight: bold;
margin-bottom: 0;
cursor: pointer;
display: inline-block;
font-size: 16px;
// currently used in the member-details-new.vue
&.smaller {
font-family: Roboto;
font-size: 14px;
font-weight: bold;
line-height: 1.71;
}
display: inline-flex !important;
&.no-tier {
color: $gray-50;
@@ -111,7 +103,6 @@ export default {
'backer',
'contributor',
'hideTooltip',
'smallerStyle',
'showBuffed',
'context',
],
@@ -173,7 +164,7 @@ export default {
return this.hideTooltip ? '' : achievementsLib.getContribText(this.contributor, this.isNPC) || '';
},
levelStyle () {
return `${this.userLevelStyleFromLevel(this.level, this.isNPC)} ${this.smallerStyle ? 'smaller' : ''}`;
return `${this.userLevelStyleFromLevel(this.level, this.isNPC)}`;
},
},
};

View File

@@ -0,0 +1,102 @@
import debounce from 'lodash/debounce';
export const autoCompleteHelperMixin = {
data () {
return {
mixinData: {
autoComplete: {
caretPosition: 0,
coords: {
TOP: 0,
LEFT: 0,
},
},
},
};
},
methods: {
autoCompleteMixinHandleTab (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
if (e.shiftKey) {
this.$refs.autocomplete.selectPrevious();
} else {
this.$refs.autocomplete.selectNext();
}
}
},
autoCompleteMixinHandleEscape (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.cancel();
}
},
autoCompleteMixinSelectNextAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectNext();
}
},
autoCompleteMixinSelectPreviousAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectPrevious();
}
},
autoCompleteMixinSelectAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
if (this.$refs.autocomplete.selected !== null) {
e.preventDefault();
this.$refs.autocomplete.makeSelection();
} else {
// no autocomplete selected, newline instead
this.$refs.autocomplete.cancel();
}
}
},
autoCompleteMixinUpdateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
autoCompleteMixinResetCoordsPosition () {
this.mixinData.autoComplete.coords = {
TOP: 0,
LEFT: 0,
};
},
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
_getCoord (e, text) {
const caretPosition = text.selectionEnd;
this.mixinData.autoComplete.caretPosition = caretPosition;
const div = document.createElement('div');
const span = document.createElement('span');
const copyStyle = getComputedStyle(text);
[].forEach.call(copyStyle, prop => {
div.style[prop] = copyStyle[prop];
});
div.style.position = 'absolute';
document.body.appendChild(div);
div.textContent = text.value.substr(0, caretPosition);
span.textContent = text.value.substr(caretPosition) || '.';
div.appendChild(span);
this.mixinData.autoComplete.coords = {
TOP: span.offsetTop,
LEFT: span.offsetLeft,
};
document.body.removeChild(div);
},
_updateCarretPosition (eventUpdate) {
const text = eventUpdate.target;
this._getCoord(eventUpdate, text);
},
},
};

View File

@@ -1,7 +1,7 @@
import notifications from './notifications';
import { NotificationMixins } from './notifications';
export default {
mixins: [notifications],
export const CopyToClipboardMixin = {
mixins: [NotificationMixins],
methods: {
async mixinCopyToClipboard (valueToCopy, notificationToShow = null) {
if (navigator.clipboard) {
@@ -21,3 +21,5 @@ export default {
},
},
};
export default CopyToClipboardMixin;

View File

@@ -2,54 +2,55 @@ import includes from 'lodash/includes';
export default {
methods: {
foolPet (pet) {
foolPet (pet, prank) {
const SPECIAL_PETS = [
'Wolf-Veteran',
'Wolf-Cerberus',
'Dragon-Hydra',
'Turkey-Base',
'BearCub-Polar',
'MantisShrimp-Base',
'JackOLantern-Base',
'Mammoth-Base',
'Tiger-Veteran',
'Phoenix-Base',
'Turkey-Gilded',
'MagicalBee-Base',
'Lion-Veteran',
'Gryphon-RoyalPurple',
'JackOLantern-Ghost',
'Jackalope-RoyalPurple',
'Orca-Base',
'Bear-Veteran',
'Hippogriff-Hopeful',
'Fox-Veteran',
'JackOLantern-Glow',
'Gryphon-Gryphatrice',
'Gryphatrice-Jubilant',
'JackOLantern-RoyalPurple',
'BearCub-Polar',
'Cactus-Veteran',
'Dragon-Hydra',
'Dragon-Veteran',
'Fox-Veteran',
'Gryphatrice-Jubilant',
'Gryphon-Gryphatrice',
'Gryphon-RoyalPurple',
'Hippogriff-Hopeful',
'Jackalope-RoyalPurple',
'JackOLantern-Base',
'JackOLantern-Ghost',
'JackOLantern-Glow',
'JackOLantern-RoyalPurple',
'Lion-Veteran',
'MagicalBee-Base',
'Mammoth-Base',
'MantisShrimp-Base',
'Orca-Base',
'Phoenix-Base',
'Tiger-Veteran',
'Turkey-Base',
'Turkey-Gilded',
'Wolf-Cerberus',
'Wolf-Veteran',
];
const BASE_PETS = [
'Wolf',
'TigerCub',
'PandaCub',
'LionCub',
'Fox',
'FlyingPig',
'BearCub',
'Dragon',
'Cactus',
'Dragon',
'FlyingPig',
'Fox',
'LionCub',
'PandaCub',
'TigerCub',
'Wolf',
];
if (!pet) return 'Pet-TigerCub-Fungi';
if (!pet) return `Pet-TigerCub-${prank}`;
if (SPECIAL_PETS.indexOf(pet) !== -1) {
return 'Pet-Dragon-Fungi';
return `Pet-Dragon-${prank}`;
}
const species = pet.slice(0, pet.indexOf('-'));
if (includes(BASE_PETS, species)) {
return `Pet-${species}-Fungi`;
return `Pet-${species}-${prank}`;
}
return 'Pet-BearCub-Fungi';
return `Pet-BearCub-${prank}`;
},
},
};

View File

@@ -15,12 +15,26 @@
>
{{ $t('messages') }}
</h2>
<div class="placeholder svg-icon">
<!-- placeholder -->
</div>
</div>
<button
class="btn btn-secondary plus-button"
:class="{'new-message-mode':showStartNewConversationInput}"
@click="triggerStartNewConversationState()"
>
<div
v-if="selectedConversation && selectedConversation.key"
class="svg-icon icon-10 color"
v-html="icons.positive"
></div>
</button>
</div>
<start-new-conversation-input-header
v-if="showStartNewConversationInput"
@startNewConversation="startConversationByUsername($event)"
@cancelNewConversation="showStartNewConversationInput = false"
/>
<div
v-else-if="selectedConversation && selectedConversation.key"
class="d-flex selected-conversion"
>
<router-link
@@ -52,25 +66,11 @@
@change="toggleOpt()"
/>
</div>
<div
v-if="filtersConversations.length > 0"
class="conversations"
>
<conversation-item
v-for="conversation in filtersConversations"
:key="conversation.key"
:active-key="selectedConversation.key"
:contributor="conversation.contributor"
:backer="conversation.backer"
:uuid="conversation.key"
:display-name="conversation.name"
:username="conversation.username"
:last-message-date="conversation.date"
:last-message-text="conversation.lastMessageText
? removeTags(parseMarkdown(conversation.lastMessageText)) : ''"
@click="selectConversation(conversation.key)"
<pm-conversations-list
:filters-conversations="filtersConversations"
:selected-conversation="selectedConversation"
@selectConversation="selectConversation($event)"
/>
</div>
<button
v-if="canLoadMoreConversations"
class="btn btn-secondary"
@@ -79,28 +79,35 @@
{{ $t('loadMore') }}
</button>
</div>
<div class="messages-column d-flex flex-column align-items-center">
<div
v-if="filtersConversations.length === 0
&& (!selectedConversation || !selectedConversation.key)"
class="empty-messages m-auto text-center empty-sidebar"
v-if="user.inbox.optOut"
class="disable-background-in-message-list"
>
<div class="no-messages-box">
<div
<span
v-once
class="svg-icon envelope"
v-html="icons.messageIcon"
></div>
<h2 v-once>
{{ $t('emptyMessagesLine1') }}
</h2>
<p v-if="!user.flags.chatRevoked">
{{ $t('emptyMessagesLine2') }}
</p>
</div>
class="caption"
> {{ $t('PMDisabledCaptionTitle') }}. </span> &nbsp;
<span
v-once
class="text"
> {{ $t('PMDisabledCaptionText') }} </span>
</div>
<pm-empty-state
v-if="uiState === UI_STATES.NO_CONVERSATIONS"
:chat-revoked="user.flags.chatRevoked"
@newMessageClicked="showStartNewConversationInput = true"
/>
<pm-new-message-started
v-if="uiState === UI_STATES.START_NEW_CONVERSATION && selectedConversation.userStyles"
:member-obj="selectedConversation.userStyles"
/>
<div
v-if="filtersConversations.length !== 0 && !selectedConversation.key"
v-if="uiState === UI_STATES.NO_CONVERSATIONS_SELECTED"
class="empty-messages full-height m-auto text-center"
>
<div class="no-messages-box">
@@ -113,20 +120,7 @@
<p v-html="placeholderTexts.description"></p>
</div>
</div>
<div
v-if="selectedConversation.key && selectedConversationMessages.length === 0"
class="empty-messages full-height mt-auto text-center"
>
<avatar
v-if="selectedConversation.userStyles"
:member="selectedConversation.userStyles"
:avatar-only="true"
sprites-margin="0 0 0 -45px"
class="center-avatar"
/>
<h3>{{ $t('beginningOfConversation', {userName: selectedConversation.name}) }}</h3>
<p>{{ $t('beginningOfConversationReminder') }}</p>
</div>
<messageList
v-if="selectedConversation && selectedConversationMessages.length > 0"
ref="chatscroll"
@@ -136,16 +130,18 @@
:can-load-more="canLoadMore"
:is-loading="messagesLoading"
@message-removed="messageRemoved"
@message-liked="messageLiked"
@triggerLoad="infiniteScrollTrigger"
/>
<pm-disabled-state
v-if="disabledTexts?.showBottomInfo"
:disabled-texts="disabledTexts"
/>
<div
v-if="disabledTexts"
class="pm-disabled-caption text-center"
v-if="shouldShowInputPanel"
class="full-width"
>
<h4>{{ disabledTexts.title }}</h4>
<p>{{ disabledTexts.description }}</p>
</div>
<div class="full-width">
<div
class="new-message-row d-flex align-items-center"
>
@@ -174,7 +170,7 @@
:class="{'disabled':newMessageDisabled || newMessage === ''}"
@click="sendPrivateMessage()"
>
{{ $t('send') }}
{{ $t('sendMessage') }}
</button>
</div>
</div>
@@ -184,8 +180,8 @@
</template>
<style lang="scss">
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/variables.scss';
@import '~@/assets/scss/colors';
@import '~@/assets/scss/variables';
$pmHeaderHeight: 56px;
@@ -216,7 +212,11 @@
}
.toggle-switch-outer {
display: flex;
display: block !important;
}
.toggle-switch {
float: right !important;
}
}
@@ -254,13 +254,44 @@
letter-spacing: normal;
color: $gray-50;
}
.empty-messages {
flex-flow: column;
justify-content: center;
h3, p {
color: $gray-200;
margin: 0rem;
}
h2 {
color: $gray-200;
margin-bottom: 1rem;
}
.no-messages-box {
display: flex;
flex-direction: column;
align-items: center;
width: 330px;
}
.envelope {
color: $gray-400 !important;
svg {
width: 86px;
height: 64px;
}
}
}
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/tiers.scss';
@import '~@/assets/scss/variables.scss';
@import '~@/assets/scss/colors';
@import '~@/assets/scss/tiers';
@import '~@/assets/scss/variables';
$pmHeaderHeight: 56px;
$background: $white;
@@ -268,10 +299,15 @@
.header-bar {
height: 56px;
background-color: $white;
padding-left: 1.5rem;
padding-right: 1.5rem;
align-items: center;
.left-header {
padding-left: 1.5rem;
max-width: 330px;
align-items: center;
flex: 1;
}
.mail-icon {
width: 32px;
height: 24px;
@@ -285,6 +321,14 @@
.placeholder.svg-icon {
width: 32px;
}
.plus-button {
padding: 10px 14px;
&.new-message-mode {
color: $gray-200;
}
}
}
.full-height {
@@ -316,42 +360,24 @@
border-bottom: 1px solid $gray-500;
}
.conversations {
overflow-x: hidden;
overflow-y: auto;
height: 100%;
}
.empty-messages {
h3, p {
color: $gray-200;
margin: 0rem;
}
h2 {
color: $gray-200;
margin-bottom: 1rem;
}
p {
font-size: 12px;
}
.no-messages-box {
.disable-background-in-message-list {
display: flex;
flex-direction: column;
align-items: center;
width: 330px;
justify-content: center;
height: 44px;
color: $yellow-1;
background: $yellow-500;
width: 100%;
.caption {
font-weight: 700;
line-height: 24px;
}
.envelope {
color: $gray-400 !important;
margin-bottom: 1.5rem;
::v-deep svg {
width: 64px;
height: 48px;
}
.text {
font-weight: 400;
line-height: 24px;
}
}
@@ -446,6 +472,7 @@
padding: 1.5rem;
.guidelines {
height: 32px;
font-size: 12px;
font-weight: normal;
font-style: normal;
@@ -458,10 +485,10 @@
}
button {
height: 32px;
border-radius: 4px;
line-height: 1.714;
margin-left: 1.5rem;
padding: 2px 12px;
white-space: nowrap;
&.disabled {
cursor: default;
@@ -473,30 +500,6 @@
}
}
.pm-disabled-caption {
padding-top: 1em;
z-index: 2;
h4, p {
color: $gray-200;
}
h4 {
margin-top: 0;
margin-bottom: 0.4em;
}
p {
font-size: 12px;
margin-bottom: 0;
}
}
.left-header {
max-width: calc(330px - 2rem); // minus the left padding
flex: 1;
}
.sidebar {
width: 330px;
background-color: $gray-700;
@@ -540,7 +543,7 @@
z-index: 1;
pointer-events: none;
box-shadow: 0 3px 12px 0 rgba($black, 0.24);
box-shadow: 0 3px 12px 0 rgba(26, 24, 29, 0.24);
}
.center-avatar {
@@ -549,36 +552,52 @@
</style>
<script>
import Vue from 'vue';
import Vue, { defineComponent } from 'vue';
import moment from 'moment';
import groupBy from 'lodash/groupBy';
import orderBy from 'lodash/orderBy';
import habiticaMarkdown from 'habitica-markdown';
import axios from 'axios';
import { MAX_MESSAGE_LENGTH } from '@/../../common/script/constants';
import findIndex from 'lodash/findIndex';
import { mapState } from '@/libs/store';
import styleHelper from '@/mixins/styleHelper';
import toggleSwitch from '@/components/ui/toggleSwitch';
import userLink from '@/components/userLink';
import toggleSwitch from '@/components/ui/toggleSwitch.vue';
import userLink from '@/components/userLink.vue';
import messageList from '@/components/messages/messageList';
import messageList from '@/components/messages/messageList.vue';
import messageIcon from '@/assets/svg/message.svg';
import mail from '@/assets/svg/mail.svg';
import conversationItem from '@/components/messages/conversationItem';
import faceAvatar from '@/components/faceAvatar';
import Avatar from '@/components/avatar';
import faceAvatar from '@/components/faceAvatar.vue';
import { EVENTS } from '@/libs/events';
import PmConversationsList from './pm-conversations-list.vue';
import PmEmptyState from './pm-empty-state.vue';
import PmDisabledState from './pm-disabled-state.vue';
import PmNewMessageStarted from './pm-new-message-started.vue';
import StartNewConversationInputHeader from './start-new-conversation-input-header.vue';
import positiveIcon from '@/assets/svg/positive.svg';
import NotificationMixins from '@/mixins/notifications';
// extract to a shared path
const CONVERSATIONS_PER_PAGE = 10;
const PM_PER_PAGE = 10;
export default {
const UI_STATES = Object.freeze({
LOADING: 'LOADING',
NO_CONVERSATIONS: 'NO_CONVERSATIONS',
NO_CONVERSATIONS_SELECTED: 'NO_CONVERSATIONS_SELECTED',
START_NEW_CONVERSATION: 'START_NEW_CONVERSATION',
CONVERSATION_SELECTED: 'CONVERSATION_SELECTED',
});
export default defineComponent({
components: {
Avatar,
StartNewConversationInputHeader,
PmNewMessageStarted,
PmDisabledState,
PmEmptyState,
PmConversationsList,
messageList,
toggleSwitch,
conversationItem,
userLink,
faceAvatar,
},
@@ -587,7 +606,7 @@ export default {
return moment(new Date(value)).fromNow();
},
},
mixins: [styleHelper],
mixins: [styleHelper, NotificationMixins],
beforeRouteEnter (to, from, next) {
next(vm => {
const data = vm.$store.state.privateMessageOptions;
@@ -610,17 +629,26 @@ export default {
icons: Object.freeze({
messageIcon,
mail,
positive: positiveIcon,
}),
loaded: false,
UI_STATES,
showStartNewConversationInput: false,
newConversationTargetUser: null,
loadingConversations: true,
showPopover: false,
/* Conversation-specific data */
/**
* @type {PrivateMessages.InitiatedConversation}
*/
initiatedConversation: null,
updateConversationsCounter: 0,
selectedConversation: {},
conversationPage: 0,
canLoadMoreConversations: false,
/** @type {PrivateMessages.ConversationSummaryMessageEntry[]} */
loadedConversations: [],
/** @type {Record<string, PrivateMessages.PrivateMessageEntry[]>} */
messagesByConversation: {}, // cache {uuid: []}
newMessage: '',
@@ -653,9 +681,15 @@ export default {
}];
}
// Create conversation objects
/** @type {PrivateMessages.ConversationEntry[]} */
const convos = [];
for (const key in inboxGroup) {
if (Object.prototype.hasOwnProperty.call(inboxGroup, key)) {
/**
* @type {PrivateMessages.ConversationSummaryMessageEntry}
*/
const recentMessage = inboxGroup[key][0];
const convoModel = {
@@ -709,9 +743,6 @@ export default {
return ordered;
},
currentLength () {
return this.newMessage.length;
},
placeholderTexts () {
if (this.user.flags.chatRevoked) {
return {
@@ -724,24 +755,22 @@ export default {
description: this.$t('PMPlaceholderDescription'),
};
},
disabledTexts () {
if (this.user.flags.chatRevoked) {
return {
enableInput: false,
showBottomInfo: true,
title: this.$t('PMPlaceholderTitleRevoked'),
description: this.$t('chatPrivilegesRevoked'),
};
}
if (this.user.inbox.optOut) {
return {
title: this.$t('PMDisabledCaptionTitle'),
description: this.$t('PMDisabledCaptionText'),
};
}
if (this.selectedConversation?.key) {
if (this.user.inbox.blocks.includes(this.selectedConversation.key)) {
return {
enableInput: false,
showBottomInfo: true,
title: this.$t('PMDisabledCaptionTitle'),
description: this.$t('PMUnblockUserToSendMessages'),
};
@@ -749,12 +778,23 @@ export default {
if (!this.selectedConversation.canReceive) {
return {
enableInput: false,
showBottomInfo: true,
title: this.$t('PMCanNotReply'),
description: this.$t('PMUserDoesNotReceiveMessages'),
};
}
}
if (this.user.inbox.optOut) {
return {
enableInput: true,
showBottomInfo: false,
title: this.$t('PMDisabledCaptionTitle'),
description: this.$t('PMDisabledCaptionText'),
};
}
return null;
},
optTextSet () {
@@ -776,8 +816,51 @@ export default {
return '';
},
newMessageDisabled () {
return !this.selectedConversation || !this.selectedConversation.key
|| this.disabledTexts !== null;
if (this.disabledTexts) {
return !this.disabledTexts.enableInput;
}
return [
UI_STATES.NO_CONVERSATIONS_SELECTED,
UI_STATES.NO_CONVERSATIONS,
UI_STATES.LOADING,
].includes(this.uiState);
},
uiState () {
if (this.loadingConversations) {
return UI_STATES.LOADING;
}
if (this.loadedConversations.length === 0) {
return UI_STATES.NO_CONVERSATIONS;
}
// Hiding the "Select a conversation on the left" state,
// and just picking the first conversation once it loads, right away
// see reload method
/* if (!this.selectedConversation.key) {
return UI_STATES.NO_CONVERSATIONS_SELECTED;
} */
if (this.selectedConversationMessages.length === 0) {
return UI_STATES.START_NEW_CONVERSATION;
}
return UI_STATES.CONVERSATION_SELECTED;
},
shouldShowInputPanel () {
const currentUiState = this.uiState;
switch (currentUiState) {
case UI_STATES.CONVERSATION_SELECTED:
case UI_STATES.START_NEW_CONVERSATION: {
return true;
}
default: {
return false;
}
}
},
},
async mounted () {
@@ -787,15 +870,11 @@ export default {
// notification click to refresh
this.$root.$on(EVENTS.PM_REFRESH, async () => {
await this.reload();
this.selectFirstConversation();
});
// header sync button
this.$root.$on(EVENTS.RESYNC_COMPLETED, async () => {
await this.reload();
this.selectFirstConversation();
});
await this.reload();
@@ -828,7 +907,7 @@ export default {
methods: {
async reload () {
this.loaded = false;
this.loadingConversations = true;
this.conversationPage = 0;
this.loadedConversations = [];
@@ -838,11 +917,15 @@ export default {
await this.$store.dispatch('user:markPrivMessagesRead');
this.loaded = true;
await this.selectFirstConversation();
this.loadingConversations = false;
},
async loadConversations () {
const query = ['/api/v4/inbox/conversations'];
query.push(`?page=${this.conversationPage}`);
const query = [
'/api/v4/inbox/conversations',
`?page=${this.conversationPage}`,
];
this.conversationPage += 1;
const conversationRes = await axios.get(query.join(''));
@@ -850,6 +933,12 @@ export default {
this.canLoadMoreConversations = loadedConversations.length === CONVERSATIONS_PER_PAGE;
this.loadedConversations.push(...loadedConversations);
},
messageLiked (message) {
const messages = this.messagesByConversation[this.selectedConversation.key];
const chatIndex = findIndex(messages, chatMessage => chatMessage.id === message.id);
messages.splice(chatIndex, 1, message);
},
messageRemoved (message) {
const messages = this.messagesByConversation[this.selectedConversation.key];
@@ -916,38 +1005,82 @@ export default {
this.selectedConversation.lastMessageText = this.newMessage;
this.selectedConversation.date = new Date();
this.scrollToBottom();
this.$store.dispatch('members:sendPrivateMessage', {
toUserId: this.selectedConversation.key,
message: this.newMessage,
}).then(response => {
const newMessage = response.data.data.message;
const messageToReset = messages[messages.length - 1];
messageToReset.id = newMessage.id; // just set the id, all other infos already set
messageToReset.text = newMessage.text; // handle mentions
// just set the id, all other infos already set
messageToReset.id = newMessage.id;
messageToReset.text = newMessage.text;
messageToReset.uniqueMessageId = newMessage.uniqueMessageId;
Object.assign(messages[messages.length - 1], messageToReset);
this.updateConversationsCounter += 1;
});
this.newMessage = '';
setTimeout(() => {
this.scrollToBottom();
}, 150);
},
scrollToBottom () {
/**
* This method does a couple of things:
* - first round:
* - tries to scroll down
* - in the next tick it triggers it again
* (during testing it seemed that the first trigger still had some space left to scroll)
* - 2nd round:
* - tries to scroll down
* - in the next tick it checks if the scrollTop is to most it can scroll down,
* if it is, it stops from doing that again
* if not, it goes into the next round
* - if we reach round 6 it stops completely,
* no need to have a endless loop of just scrolling down
*/
scrollToBottom (callCount = 0) {
if (callCount > 5) {
return;
}
if (!this.$refs.chatscroll) {
// if the message list component not loaded yet, but scrollToBottom was called
// just try again at a later time
setTimeout(() => {
this.scrollToBottom(callCount + 1);
}, 125);
return;
}
const chatscrollEl = this.$refs.chatscroll.$el;
// chatscrollBeforeTick.scrollTop = chatscrollBeforeTick.scrollHeight;
chatscrollEl.scrollTo(0, chatscrollEl.scrollHeight);
Vue.nextTick(() => {
if (!this.$refs.chatscroll) return;
const chatscroll = this.$refs.chatscroll.$el;
chatscroll.scrollTop = chatscroll.scrollHeight;
if (!this.$refs.chatscroll) {
return;
}
let shouldRetrigger = true;
if (callCount > 1) {
const maxPossibleScrollPos = chatscrollEl.scrollHeight - chatscrollEl.clientHeight;
if (chatscrollEl.scrollTop === maxPossibleScrollPos) {
shouldRetrigger = false;
}
}
if (shouldRetrigger) {
setTimeout(() => {
this.scrollToBottom(callCount + 1);
}, 125);
}
});
},
removeTags (html) {
const tmp = document.createElement('DIV');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
},
parseMarkdown (text) {
if (!text) return null;
return habiticaMarkdown.render(String(text));
},
infiniteScrollTrigger () {
// show loading and wait until the loadMore debounced
// or else it would trigger on every scrolling-pixel (while not loading)
@@ -983,11 +1116,81 @@ export default {
this.selectedConversation.canLoadMore = loadedMessages.length === PM_PER_PAGE;
this.messagesLoading = false;
},
selectFirstConversation () {
async selectFirstConversation () {
if (this.loadedConversations.length > 0) {
this.selectConversation(this.loadedConversations[0].uuid, true);
await this.selectConversation(this.loadedConversations[0].uuid, true);
}
},
triggerStartNewConversationState () {
this.showStartNewConversationInput = true;
},
async startConversationByUsername (targetUserName) {
// check if the target user exists in current conversations, select that conversation
/** @type {PrivateMessages.ConversationSummaryMessageEntry} */
const foundConversation = this.loadedConversations.find(c => c.username === targetUserName);
if (foundConversation) {
this.selectConversation(foundConversation.uuid);
this.showStartNewConversationInput = false;
return;
}
let loadedMember = null;
try {
loadedMember = await this.$store.dispatch('members:fetchMemberByUsername', {
username: targetUserName,
});
} catch {
loadedMember = null;
}
if (!loadedMember) {
this.error(this.$t('targetUserNotExist', { userName: targetUserName }));
return;
}
const loadedMemberUUID = loadedMember.id;
this.showStartNewConversationInput = false;
// otherwise create a dummy conversation, load messages for that user
/**
* @type {PrivateMessages.ConversationSummaryMessageEntry}
*/
const newConversationItem = {
uuid: loadedMemberUUID,
user: loadedMember.profile.name,
username: loadedMember.auth.local.username,
contributor: loadedMember.contributor,
userStyles: loadedMember,
canReceive: loadedMember.inbox.canReceive,
timestamp: new Date(),
count: 0,
text: '',
};
this.loadedConversations.splice(0, 0, newConversationItem);
this.selectConversation(loadedMemberUUID);
if (this.messagesByConversation[loadedMemberUUID]) {
const messageLengthByConversation = this.messagesByConversation[loadedMemberUUID].length;
// if messages already exists, update the sidebar entry last message
if (messageLengthByConversation > 0) {
/** @type {PrivateMessages.PrivateMessageEntry} */
const lastMessage = this.messagesByConversation[loadedMemberUUID][messageLengthByConversation - 1];
newConversationItem.lastMessageText = lastMessage.text;
return;
}
}
this.newConversationTargetUser = loadedMember;
},
},
});
</script>

View File

@@ -62,7 +62,7 @@
<script>
import moment from 'moment';
import userLabel from '../userLabel';
import userLabel from '../../components/userLabel.vue';
import dots from '@/assets/svg/dots.svg';
import block from '@/assets/svg/block.svg';
@@ -117,7 +117,7 @@ export default {
</script>
<style lang="scss">
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/colors';
.action-padding {
height: 24px !important;
@@ -153,7 +153,7 @@ export default {
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
@import '~@/assets/scss/colors';
.conversation {
padding: 1rem 1.5rem;

View File

@@ -0,0 +1,64 @@
<template>
<div
v-if="filtersConversations.length > 0"
class="conversations"
>
<conversation-item
v-for="conversation in filtersConversations"
:key="conversation.key"
:active-key="selectedConversation?.key"
:contributor="conversation.contributor"
:backer="conversation.backer"
:uuid="conversation.key"
:display-name="conversation.name"
:username="conversation.username"
:last-message-date="conversation.date"
:last-message-text="conversation.lastMessageText
? removeTags(parseMarkdown(conversation.lastMessageText)) : ''"
@click="selectConversation(conversation.key)"
/>
</div>
</template>
<style scoped lang="scss">
.conversations {
overflow-x: hidden;
overflow-y: auto;
height: 100%;
}
</style>
<script>
import { defineComponent } from 'vue';
import habiticaMarkdown from 'habitica-markdown';
import conversationItem from '@/pages/private-messages/pm-conversation-item.vue';
export default defineComponent({
components: { conversationItem },
props: {
filtersConversations: {
type: Array,
default: () => [],
},
selectedConversation: {
type: Object,
default: null,
},
},
methods: {
removeTags (html) {
const tmp = document.createElement('DIV');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || '';
},
parseMarkdown (text) {
if (!text) return null;
return habiticaMarkdown.render(String(text));
},
selectConversation (conversationKey) {
this.$emit('selectConversation', conversationKey);
},
},
});
</script>

View File

@@ -0,0 +1,37 @@
<template>
<div
class="pm-disabled-caption text-center"
>
<h4>{{ disabledTexts.title }}</h4>
<p>{{ disabledTexts.description }}</p>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.pm-disabled-caption {
padding-top: 1.5em;
z-index: 2;
h4, p {
color: $gray-200;
}
h4 {
margin-top: 0;
margin-bottom: 0.4em;
}
p {
font-size: 12px;
margin-bottom: 0;
}
}
</style>
<script>
export default {
props: ['disabledTexts'],
};
</script>

View File

@@ -0,0 +1,71 @@
<template>
<div
class="empty-messages m-auto text-center empty-sidebar"
>
<div class="no-messages-box">
<div
v-once
class="svg-icon envelope mb-4"
v-html="icons.mailIcon"
></div>
<strong
v-once
class="mb-1"
>
{{ $t('emptyMessagesLine1') }}
</strong>
<p v-if="!chatRevoked">
{{ $t('emptyMessagesLine2') }}
</p>
</div>
<button
class="btn btn-primary mt-4 d-flex align-items-center"
@click="$emit('newMessageClicked')"
>
<div
class="svg-icon icon-10 color mr-2"
v-html="icons.positive"
></div>
{{ $t('newMessage') }}
</button>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
strong {
line-height: 1.71;
color: $gray-100;
}
.svg-icon.icon-10 {
margin: 3px;
}
p {
font-size: 14px;
font-weight: 400;
line-height: 24px;
}
</style>
<script>
import mailIcon from '@/assets/svg/mail.svg';
import positiveIcon from '@/assets/svg/positive.svg';
export default {
props: {
chatRevoked: Boolean,
},
data () {
return {
icons: Object.freeze({
mailIcon,
positive: positiveIcon,
}),
};
},
};
</script>

View File

@@ -0,0 +1,69 @@
<template>
<div
v-once
class="centered empty-messages m-auto text-center"
>
<avatar
v-if="memberObj"
:member="memberObj"
:avatar-only="true"
:show-weapon="false"
:hide-class-badge="true"
:override-top-padding="'0px'"
:sprites-margin="'0 0 0 -30px'"
:debug-mode="false"
:center-avatar="true"
class="mb-3"
/>
<strong>{{ memberObj.profile.name }}</strong>
<div class="username mb-3">
@{{ memberObj.auth.local.username }}
</div>
<div
class="kind-text"
v-html="$t('rememberToBeKind')"
></div>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.centered {
align-content: center;
}
.center-avatar {
margin: 0 auto;
}
strong {
line-height: 1.71;
}
.username {
font-size: 12px;
line-height: 1.33;
color: $gray-100;
margin-top: -4px;
}
.kind-text {
width: 330px;
line-height: 1.71;
color: $gray-100;
}
</style>
<script>
import Avatar from '@/components/avatar.vue';
export default {
components: { Avatar },
props: {
memberObj: null,
},
};
</script>

View File

@@ -0,0 +1,44 @@
export namespace PrivateMessages {
// Shared properties between message types
interface SharedMessageProps {
username: string;
contributor: Record<string, unknown>;
userStyles: Record<string, unknown>;
canReceive: boolean;
}
/**
* This is the Type we get from our API
*/
interface ConversationSummaryMessageEntry extends SharedMessageProps {
uuid: string;
user: string;
timestamp: string;
text: string;
count: number;
}
/**
* The Visual (Sidebar) Entry
*/
interface ConversationEntry extends SharedMessageProps {
/**
* UUID
*/
key: string;
name: string;
lastMessageText: '',
canLoadMore: boolean;
page: 0
}
/**
* Loaded Private Messages, partial type
*/
interface PrivateMessageEntry extends SharedMessageProps {
text: string;
}
}

View File

@@ -0,0 +1,165 @@
<template>
<div class="ml-4">
<strong
v-once
v-html="$t('to')"
></strong>
<validated-text-input
id="selectUser"
ref="targetUserInput"
v-model="targetUserInputValue"
class="mx-2"
:is-valid="foundUser._id"
:only-show-invalid-state="foundUser._id === undefined"
:hide-error-line="true"
:placeholder="$t('usernameOrUserId')"
:invalid-issues="userInputInvalidIssues"
@enter="triggerNewConversation"
/>
<button
class="btn btn-primary"
:disabled="preventTrigger"
@click="triggerNewConversation()"
>
{{ $t('confirm') }}
</button>
<button
class="ml-2 btn btn-secondary"
@click="$emit('cancelNewConversation')"
>
{{ $t('cancel') }}
</button>
</div>
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
div {
display: flex;
align-items: center;
}
div > * {
height: 32px;
}
strong {
line-height: 1.71;
align-content: center;
}
input {
border-radius: 2px;
border-width: 2px;
width: 420px;
}
#selectUser {
/* changing the style of validate-text-input to the same as others */
::v-deep {
.input-group {
border-width: 2px;
input {
width: 420px;
height: 100%;
color: $gray-50;
}
}
.input-group {
&:focus, &:active, &:focus-within {
border: solid 2px $purple-400;
}
}
}
}
</style>
<script>
import debounce from 'lodash/debounce';
import isUUID from 'validator/es/lib/isUUID';
import ValidatedTextInput from '@/components/ui/validatedTextInput.vue';
export default {
components: {
ValidatedTextInput,
},
mixins: [],
data () {
return {
targetUserInputValue: '',
userNotFound: false,
foundUser: {},
};
},
computed: {
preventTrigger () {
return this.targetUserInputValue.length < 2;
},
userInputInvalidIssues () {
return this.targetUserInputValue.length > 0 && this.userNotFound
? [this.$t('userWithUsernameOrUserIdNotFound')]
: [''];
},
},
watch: {
targetUserInputValue: {
handler () {
this.searchUser(this.targetUserInputValue.replace('@', ''));
},
},
},
mounted () {
this.$refs.targetUserInput.focus();
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'select-user-modal');
},
searchUser: debounce(async function userSearch (searchTerm = '') {
this.foundUser = {};
if (searchTerm.length < 1) {
this.userNotFound = false;
return;
}
let result;
if (isUUID(searchTerm)) {
try {
result = await this.$store.dispatch('members:fetchMember', {
memberId: searchTerm,
});
} catch {
result = null;
}
} else {
try {
result = await this.$store.dispatch('members:fetchMemberByUsername', {
username: searchTerm,
});
} catch {
result = null;
}
}
if (!result) {
this.userNotFound = true;
return;
}
this.userNotFound = false;
this.foundUser = result;
}, 500),
triggerNewConversation () {
const userWithoutAt = this.$refs.targetUserInput.value.replace('@', '');
this.$emit('startNewConversation', userWithoutAt);
},
},
};
</script>

View File

@@ -11,6 +11,7 @@
class="balance-info"
:currency-needed="currencyNeeded"
:amount-needed="amountNeeded"
:neededCurrencyOnly="true"
/>
</div>
</template>

View File

@@ -66,6 +66,7 @@
<your-balance
:amount-needed="amountNeeded"
currency-needed="gems"
class="d-flex align-items-center"
/>
</div>
</td>

View File

@@ -49,7 +49,7 @@ const GroupPlanIndex = () => import(/* webpackChunkName: "group-plans" */ '@/com
const GroupPlanTaskInformation = () => import(/* webpackChunkName: "group-plans" */ '@/components/group-plans/taskInformation');
const GroupPlanBilling = () => import(/* webpackChunkName: "group-plans" */ '@/components/group-plans/billing');
const MessagesIndex = () => import(/* webpackChunkName: "private-messages" */ '@/pages/private-messages');
const MessagesIndex = () => import(/* webpackChunkName: "private-messages" */ '@/pages/private-messages/index.vue');
// Challenges
const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ '@/components/challenges/index');
@@ -190,7 +190,6 @@ const router = new VueRouter({
meta: {
privilegeNeeded: [ // any one of these is enough to give access
'userSupport',
'newsPoster',
],
},
children: [
@@ -219,7 +218,7 @@ const router = new VueRouter({
// Only used to handle some redirects
// See router.beforeEach
{ path: '/static/faq/tavern-and-guilds', redirect: '/static/tavern-and-guilds' },
{ path: '/static/tavern-and-guilds', redirect: '/static/faq/tavern-and-guilds' },
{ path: '/redirect/:redirect', name: 'redirect' },
{ path: '*', redirect: { name: 'notFound' } },
],

View File

@@ -5,3 +5,9 @@ export async function searchUsers (store, payload) {
const response = await axios.get(url);
return response.data.data;
}
export async function getUserHistory (store, payload) {
const url = `/api/v4/admin/user/${payload.userIdentifier}/history`;
const response = await axios.get(url);
return response.data.data;
}

View File

@@ -43,7 +43,14 @@ export async function deleteChat (store, payload) {
}
export async function like (store, payload) {
const url = `/api/v4/groups/${payload.groupId}/chat/${payload.chatId}/like`;
let url = '';
if (payload.groupId === 'privateMessage') {
url = `/api/v4/inbox/like-private-message/${payload.chatMessageId}`;
} else {
url = `/api/v4/groups/${payload.groupId}/chat/${payload.chatMessageId}/like`;
}
const response = await axios.post(url);
return response.data.data;
}

View File

@@ -32,3 +32,9 @@ export async function getHeroParty (store, payload) {
const response = await axios.get(url);
return response.data.data;
}
export async function getHeroGroupPlans (store, payload) {
const url = `/api/v4/hall/heroes/${payload.heroId}/group-plans`;
const response = await axios.get(url);
return response.data.data;
}

View File

@@ -72,7 +72,11 @@ describe('LevelUp', () => {
it('generates the right test class for level 15', () => {
const questClass = testFunction('questClass', 15);
expect(questClass()).to.equal('inventory_quest_scroll_atom1');
});
expect(questClass()).to.equal('scroll inventory_quest_scroll_atom1');
it('generates empty test class for level 14', () => {
const questClass = testFunction('questClass', 14);
expect(questClass()).to.equal('');
});
});

View File

@@ -1,14 +1,16 @@
import Vue from 'vue';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import ChatCard from '@/components/chat/chatCard.vue';
import BootstrapVue from 'bootstrap-vue';
import MessageCard from '@/components/messages/messageCard.vue';
import Store from '@/libs/store';
const localVue = createLocalVue();
localVue.use(Store);
localVue.use(Vue.directive('b-tooltip', {}));
localVue.use(BootstrapVue);
describe('ChatCard', () => {
describe('MessageCard', () => {
function createMessage (text) {
return { text, likes: {} };
}
@@ -26,7 +28,7 @@ describe('ChatCard', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(ChatCard, {
wrapper = shallowMount(MessageCard, {
propsData: { msg: message },
store: new Store({
state: {

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