Compare commits

...

225 Commits

Author SHA1 Message Date
Sabe Jones
d136162d48 4.13.2 2017-12-06 19:16:35 +00:00
SabreCat
2be8ddb60d fix(news): winner typo 2017-12-06 19:15:34 +00:00
Sabe Jones
ea6b78b7ca 4.13.1 2017-12-05 19:47:26 +00:00
Sabe Jones
401067bfed chore(i18n): update locales 2017-12-05 19:46:40 +00:00
Sabe Jones
54c2441934 4.13.0 2017-12-05 19:29:06 +00:00
Sabe Jones
9e8807c40d Armoire and Backgrounds December 2017 (#9659)
* feat(content): Armoire and Backgrounds 2017/12

* chore(sprites): compile

* chore(news): Bailey
2017-12-05 19:29:00 +00:00
Keith Holliday
860efefdb2 Removed client side armoire call (#9660) 2017-12-05 12:23:31 -06:00
Keith Holliday
1fc84c2357 Added calculated property (#9637) 2017-12-04 11:24:04 -06:00
thehollidayinn
95f9479d7a 4.12.6 2017-12-02 08:37:36 -06:00
Keith Holliday
af095d8450 Revert query optimization (#9636) 2017-12-02 08:35:31 -06:00
Sabe Jones
470495387c 4.12.5 2017-12-02 03:28:44 +00:00
Keith Holliday
bdef1ca23c Fixed max width none (#9631) 2017-12-01 21:26:52 -06:00
SabreCat
1835804e86 4.12.4 2017-12-01 21:33:06 +00:00
SabreCat
cb58994bdf Merge branch 'release' into develop 2017-12-01 21:32:28 +00:00
SabreCat
44f3b73183 fix(avatar): layer base/bangs correctly 2017-12-01 21:10:33 +00:00
Luan Muniz
7e23fdc22a Fix install node permissions (#9621)
Signed-off-by: Luan <luan@luanmuniz.com.br>
2017-12-01 16:05:17 +00:00
Keith Holliday
e138d2b67b Added needs cron check to achievements (#9624) 2017-12-01 09:54:43 -06:00
Keith Holliday
3e3248fecb Changed row adding when user blurs/focuses (#9610) 2017-12-01 08:37:37 -06:00
Keith Holliday
78ee60611a Remove cancel button when clicked (#9616) 2017-12-01 08:03:54 -06:00
Keith Holliday
3c7aaa605b Fixed text being cut off (#9612) 2017-12-01 08:03:36 -06:00
Keith Holliday
00343da266 Added max width seetings to screens larger than 1300 (#9609) 2017-12-01 07:47:45 -06:00
Sabe Jones
56d09411d9 Merge branch 'release' into develop 2017-12-01 00:31:23 +00:00
Sabe Jones
ae0df2242a 4.12.3 2017-12-01 00:30:49 +00:00
Sabe Jones
5b06b28c97 chore(event): end Thunderstorm Potions 2017-12-01 00:30:06 +00:00
Keith Holliday
c6a3bfb291 Added exp reset when changing level (#9611) 2017-11-30 15:45:26 -06:00
Keith Holliday
7797794cd5 Deselect a tag if it is selected when removing (#9614) 2017-11-30 15:45:14 -06:00
Keith Holliday
d9e09a5f3d Fixed streak bonus style (#9608) 2017-11-30 12:37:53 -06:00
Keith Holliday
4e73c8513e Hide progress if user is not on quest (#9597) 2017-11-30 12:37:31 -06:00
Keith Holliday
9421fd7ced Added analytics to backgrounds (#9615) 2017-11-30 12:10:49 -06:00
Keith Holliday
699de64328 Added more fields to scoring (#9613) 2017-11-30 10:09:04 -06:00
Keith Holliday
6f9cbf9ca1 Only update the user when editing profile (#9601) 2017-11-30 08:19:03 -06:00
Keith Holliday
a097819b72 User auth performance improvements (#9589)
* Added initial user projecting in auth and fixed projection for get user tasks

* Added fields to score route

* Added another field to get tasks

* Added group fields to user
2017-11-30 08:17:28 -06:00
Keith Holliday
77f71b5415 Fixed saving in progress tag when clicking save (#9598) 2017-11-30 08:16:54 -06:00
Keith Holliday
ced3621dea Fixed leaving from guild list item (#9599) 2017-11-30 08:16:00 -06:00
Keith Holliday
e321d85b3c Donate buy modal fix (#9604)
* Added donate back and buy modal

* Fixed login check

* Added ability to remove mustache
2017-11-30 08:15:28 -06:00
SabreCat
d72b40d5b0 Merge branch 'release' into develop 2017-11-29 05:07:42 +00:00
SabreCat
54443a2980 4.12.2 2017-11-29 05:04:46 +00:00
SabreCat
00dc990974 chore(event): end Thanksgiving, add Bailey 2017-11-29 05:03:01 +00:00
Keith Holliday
3737aa045d Fixed text when cloning (#9594) 2017-11-28 19:18:04 -06:00
Keith Holliday
b03ddf6f7d Added fix for task order using / for arrays (#9590) 2017-11-28 14:57:08 -06:00
tim1234ltp
4ab89fd3e0 Bug fixes on Subscription termination date format [Fixes Issues #9186] (#9583)
* Fixed date.

* Got rid of the filter and returned moment.

* fix the return value

* Stupid typo.
2017-11-28 09:19:29 +01:00
negue
f1e200c0f5 autofix pinned seasonal gear - fixes #9448 (#9570)
* auto-remove officialPinned item from userPinned-array on pinning

* hide event limited message if an item was already owned by the user
2017-11-28 09:11:40 +01:00
Ryan Holinshead
218664dfcc (ISSUE-9353) Fix pinned item alignment (#9358)
* (ISSUE-9353) Fix pinned item alignment
- Get rid of justify-content: space-between to allow flex-start default to be used
- Add margin to direct children (item-wrappers)

* Issue-9353: Change selector to & > div instead of just > for more explicit selection of direct child divs

* Fix rewards item spacing to match Zeplin mockups
- Make sure horizontal/vertical spacing between items is 16px
- Add use of grid if supported, else use flex
2017-11-27 20:54:03 -06:00
Trevor Ford
a0f29e970d fix Stable sidebar width and center inventory drawer (fixes #9263) (#9419)
* fix Stable sidebar width and center inventory drawer (fixes #9263)

* hide all .standard-sidebars on small/mobile devices
2017-11-27 20:48:52 -06:00
MathWhiz
200cd66d66 Use config when starting development server (#9410)
* Use config when starting development server

* import nconf setup from website

* Add comment explaining choice

* Fix lint issues
2017-11-27 20:38:27 -06:00
MathWhiz
dd05a8d608 Contributor Title tooltip (#9413)
* Remove usage of cachedProfileData when determining contributor level

* Add tooltip

* Remove directive import

* /s/msg.contributor.title/msg.contributor.text

* move tooltip placement

* update tooltip position
2017-11-27 20:37:36 -06:00
Asif Mallik
299e88233c Fixes multiple complete and uncomplete for todos and daily (Fixes #8669) (#8971)
* Fixed bug that allows users to complete todo and daily multiple times

* Added tests

* Fix syntax

* Fix existing tests that rely on multiple complete or uncomplete

* Undoes removal of website/client/README.md

* Change sessionOutdated string to reflect separate client needs

* Fix should update history test by changing lastCron
2017-11-27 20:13:18 -06:00
Ryan Holinshead
26bde1f766 Character Create Modal - Prevent Options From Jumping When Selected (#9252)
* Prevent options from jumping when selected due to border being added/removed based on active option. Instead, always have a border on the option but set its color when active

* Use gray instead of white border in order to match background so that it isn't visible while unselected. Add margin-bottom back

* Make sure the locked option style remains unchanged
- Nest .locked in .customize-options .option to get specificity
- Override border, border-radius, and margin-top for .locked
- Set the override values to what would be applied without other style changes
2017-11-27 20:11:15 -06:00
Garrett Scott
d95836b881 Translator minor changes fixes #8917 (#9297)
* Updated userItemsNotEnough string

* Added a variable to be passed to the deleteSocialAccountText string. This variable name is `magic_word` and is set as DELETE where used

* modified incorrectDeletePhrase to use a variable rather than translatable string for the word DELETE. Updated the DELETE-user test and the user api

* Changed noSudoAccess from translatable string to static

* Changed enterprisePlansEmailSubject from a translatable string to a static string within groupPlans.vue

* Fixed test problems with translation fixes

* Added no sudo access string to api messages

* changed plain string to apiMessage for no sudo access messages
2017-11-27 20:08:39 -06:00
Tyler Nychka
fac81bb9ee Long names overflow task box fixes #9403 (#9404)
* Issue 9403 Long names overflow task box

* Added padding

* Enabled overflow-wrap: break-word;

Added min width to allow overflow-wrap to actually break content
2017-11-27 20:07:13 -06:00
Sarvesh Kakodkar
b323abd225 Added confirmation step at begin button for quest (#9199)
* Added confirmation step at begin button for quest

* Fixed the 3 errors caused by questConfirm method in travis-ci
2017-11-27 20:05:05 -06:00
negue
b3870e5f34 multiple market fixes (#9468)
* show `selectMemberModal` to send a card, even if the user doesn't have a party yet

* market - prevent filter reset on pinning items

* hide buy amount for gear, backgrounds, mystery_set, card, rebirth_orb, fortify, armoire - fix mystery set preview in timetravelers

* purchase confirmation on gem / hourglass purchases

* fix lint
2017-11-27 19:54:55 -06:00
kartik adur
29dc56c12f Party roster sorter: Member Modal Component (#9472)
* modify sort options for party members

* add unittest for membersModalComponent sort

* updates as requested in PR

* removed duplicates for `class` and `background` from flavour text

* fix linting error thrown by travis ci
2017-11-27 19:54:13 -06:00
Esben Sparre Andreasen
b62f08d500 Misc. bug fixes from lgtm.com (2) (#9474)
* Remove dead branch of ternary: `gift` is always truthy here

Problem found here:

- https://lgtm.com/projects/g/HabitRPG/habitrpg/snapshot/dist-98076885-1510577633582/files/website/server/libs/amazonPayments.js?sort=name&dir=ASC&mode=heatmap&excluded=false#x5a22f31110a55091:1

* Remove superfluous argument, preenUserHistory only takes two args

Problem found here:

- https://lgtm.com/projects/g/HabitRPG/habitrpg/snapshot/dist-98076885-1510577633582/files/website/server/libs/cron.js?sort=name&dir=ASC&mode=heatmap&excluded=false#xf16a045ecabb07f6:1

* Cleanup: remove useless assignments

Problems found here:

- https://lgtm.com/projects/g/HabitRPG/habitrpg/snapshot/dist-98076885-1510577633582/files/website/client/store/actions/shops.js?sort=name&dir=ASC&mode=heatmap&excluded=false#xf782ed2cf920441%3A1
- https://lgtm.com/projects/g/HabitRPG/habitrpg/snapshot/dist-98076885-1510577633582/files/website/client/app.vue?sort=name&dir=ASC&mode=heatmap&excluded=false#x172c1dda85e84dc8%3A1
- https://lgtm.com/projects/g/HabitRPG/habitrpg/snapshot/dist-98076885-1510577633582/files/website/client/components/settings/site.vue#x9b3afee802a3a8f8%3A1
- https://lgtm.com/projects/g/HabitRPG/habitrpg/snapshot/dist-98076885-1510577633582/files/website/client/components/selectMembersModal.vue?sort=name&dir=ASC&mode=heatmap&excluded=false#x1fbc2a3d62facd70:1
- https://lgtm.com/projects/g/HabitRPG/habitrpg/snapshot/dist-98076885-1510577633582/files/website/common/script/libs/taskClasses.js?sort=name&dir=ASC&mode=heatmap&excluded=false#x41ce0e121a4defee:1

* Fix online editor whitespace change.
2017-11-27 19:51:25 -06:00
Esben Sparre Andreasen
f62177fb1a Misc. bug fixes from lgtm.com (#9325)
* Bugfix: declare variable locally

* Bugfix: fix syntax error

* Bugfix: regex char-class with alternatives

The old implementation used character classes instead of
alternatives. As a consequence, the regex would match:

- a_warrior_0
- r_warrior_0
- m_warrior_0
- o_warrior_0
- r_warrior_0
- |_warrior_0
- h_warrior_0
- ...

The regex will now match:

- armor_warrior_0
- head_warrior_0
- shield_warrior_0
2017-11-27 19:51:02 -06:00
Paul
885f2998ae System messages flaggable (#9408)
* Remove flag from system messages, throw an error if system messages are flagged

* Modify unflag system message test to check if flagging a system message throws an error

* Move email from nconf to top
2017-11-27 19:45:04 -06:00
Paul
2afd96e11c Remove spaces from filters (#9524) 2017-11-27 19:44:33 -06:00
Paul
cd92f44365 Fix difficult to edit checklists in Firefox (#9525)
* Change checklist item hover from move to text

* Add vue draggable

* Add vue draggable

* Replace sortablejs directive with Vue Draggable component

* Indent draggable properties
2017-11-27 19:43:24 -06:00
Paul
863177902a Remove extra 'to' paramater from router link (#9529) 2017-11-27 19:42:53 -06:00
Cassidy Pignatello
96974461e5 Change badge tooltips fixes https://github.com/HabitRPG/habitica/issues/9520 (#9539)
* updates text for costume contestant

* updates text for contributor badge

* removes unnecessary "Badge" text from contribName
2017-11-27 19:37:18 -06:00
Paul
8895b70ffa Adjust left positioning of the left-panel to unobscure sroll-bar, change left panel overflow to overflow-y (#9544) 2017-11-27 19:35:30 -06:00
Kip Raske
03480ebfc7 Passing quantity to the API call when buying quests from the shop (#9565)
You can buy multiple quests from the buy modal, but the quanitity is
never passed to the server. So the client thinks that you are buying the
number you said you did, but the server only buys one regardless. This
can lead to syncing problems down the road.
2017-11-27 19:33:08 -06:00
Joseti
9b8676f02e Changed "Donate" button to "Contribute" button (#9581)
* Changed functionality of "donate"-button on static pages

* Changed strings to reflect change from "donate" to "contribute"
2017-11-27 19:29:45 -06:00
Keith Holliday
3e7738b5b1 Added keys to for loops (#9584) 2017-11-27 12:46:25 -06:00
Keith Holliday
33a235b46c Fixed equiping glasses and ears (#9585) 2017-11-27 12:19:36 -06:00
Keith Holliday
137d6c1f9d Set default empty array when party members haven't loaded (#9579)
* Set default empty array when party members haven't loaded

* Corrected variable usage
2017-11-27 11:59:42 -06:00
Keith Holliday
1a5e820d88 Fixed when user unclaims a task assigned to them (#9578)
* Fixed when user unclaims a task assigned to them

* Removed test

* Fixed lint
2017-11-27 11:38:59 -06:00
Keith Holliday
0c7f9ca6bb Changed search to regex (#9575)
* Changed search to regex

* Changed  to array
2017-11-27 11:10:26 -06:00
Keith Holliday
3e6b3ce3ff Added habitica event for profile display (#9576) 2017-11-27 10:29:20 -06:00
Keith Holliday
ea5ba965e7 Fixed promoting group leader (#9574) 2017-11-27 10:27:30 -06:00
Keith Holliday
7215a550b5 Added page increment when page is loaded to prevent scroll stopping (#9573) 2017-11-27 10:23:04 -06:00
Keith Holliday
3235dfa236 Added equipment filter (#9572) 2017-11-27 10:22:34 -06:00
Keith Holliday
9baf7a7c67 Do not reset item when buying cards (#9571) 2017-11-27 10:22:00 -06:00
Alys
cd629ef7fa add missing @ before a contributor's name in quest content 2017-11-27 09:16:13 +00:00
Keith Holliday
9ef7c45241 Ensured user is saved after validation checks (#9569) 2017-11-23 20:46:02 -06:00
Alys
fef3d09f2d remove words related to alcohol
This is because they're causing problems in housework guilds (alcohol
is used for cleaning) and since it's coming up to Christmas a lot of
guilds are starting to have permissible conversations about drinks,
rum balls, etc.

We might put these back after Christmas but make specific exceptions
for the housework guilds.

When a more advanced word blocker is introduced, we'll be able to
ban alcohol words only in the Tavern. For irony.
2017-11-23 17:52:22 +00:00
Matteo Pagliazzi
53c83c585a fix navbar not showing up in homepage 2017-11-23 16:07:58 +01:00
Keith Holliday
e628c5dc3b Fixed task best color (#9563) 2017-11-21 14:02:53 -06:00
Keith Holliday
9eaa531f66 Amazon payment fixes (#9562)
* Added custom amazon event, removed redundency, fixed variable names

* Fixed more variables and group plan data
2017-11-21 14:02:40 -06:00
Keith Holliday
3ffea4332e Added extra confirmation incase the class modal shows multiple times (#9557) 2017-11-20 15:57:33 -06:00
Sabe Jones
4618fd8954 Merge branch 'release' into develop 2017-11-20 19:21:17 +00:00
Sabe Jones
791c19b5f1 4.12.1 2017-11-20 19:20:54 +00:00
Sabe Jones
7193cc6bae chore(i18n): update locales 2017-11-20 19:20:03 +00:00
Keith Holliday
1845bd1e35 Fixed display of RYA behind bailey (#9555) 2017-11-20 12:38:26 -06:00
Keith Holliday
5f468d16b7 Cancel users free group plan when they leave a group (#9543)
* Cancel users free group plan when they leave a group

* Fixed lint
2017-11-20 12:34:41 -06:00
Keith Holliday
20a99e526d Should do diff week fix (#9551)
* Fixed week difference when changing year

* Added year switchover test

* Fixed start date setting
2017-11-20 12:22:01 -06:00
Keith Holliday
1e69f42d0f Added account transfer migration (#9548)
* Added account transfer migration

* Removed bad comment
2017-11-19 16:53:08 -06:00
Keith Holliday
9c2f5213cb Challenge fixes (#9528)
* Added challenge member search to progress dropdown

* Added leave challenge modal

* Allowed editing for challenge leader only

* Pevented users from editing challenge task info

* Set default progress default to daily

* Removed reward filters from user challenge progress
2017-11-17 17:13:07 -06:00
Sabe Jones
c06d5107ac Merge branch 'release' into develop 2017-11-17 22:50:27 +00:00
Sabe Jones
1eb0f5baa5 4.12.0 2017-11-17 22:50:05 +00:00
Sabe Jones
b28189fff5 chore(i18n): update locales 2017-11-17 22:30:34 +00:00
SabreCat
82497e4041 chore(sprites): compile 2017-11-17 22:22:27 +00:00
SabreCat
2a5e9c0780 feat(event): Turkey Day 2017
and (content) November Subscriber Items
2017-11-17 22:21:05 +00:00
SabreCat
c8ca67aa64 fix(seasonal-shop): short circuit if categories empty 2017-11-17 18:32:55 +00:00
MathWhiz
89e4cbcffe Disable immutable inputs when editing a challenge (#9412)
* Disable uneditable inputs when editing a challenge

* Revert prize display when creating
2017-11-17 23:45:38 +11:00
Keith Holliday
67564317fb Group plan fixes (#9518)
* Fixed group plan editing

* Added translations

* Abstracted query for group or challenge tasks
2017-11-17 20:31:39 +11:00
Keith Holliday
dc2269a307 Hot fixes nov 15 (#9519)
* Added fortify sync. Removed multiple buy

* Added yesterdaily check

* Fixed checks for running yesterdailies
2017-11-17 20:31:29 +11:00
Sabe Jones
0c713ab368 4.11.1 2017-11-16 22:08:24 +00:00
Sabe Jones
be5d776fb4 chore(i18n): update locales 2017-11-16 22:07:54 +00:00
SabreCat
b0051c45b4 fix(group-plans): more claim/unclaim alignment 2017-11-16 21:21:39 +00:00
Matteo Pagliazzi
b82044e07b fix Remove Claim positioning 2017-11-16 22:05:35 +01:00
Matteo Pagliazzi
b4c4769208 fix: correctly handle bootstrap variables 2017-11-16 21:59:55 +01:00
SabreCat
42a05446c0 fix(notifications): extend snackbar duration 2017-11-16 20:32:57 +00:00
MathWhiz
9f7c0b4861 Fix regex for message highlighting (#9516) 2017-11-16 14:12:26 -06:00
Alys
4814b0c52b allow banned word / swearword blocker to apply to most public guilds (#9253)
* allow banned word / swearword blocker to apply to all public guilds, with specified exceptions

* add another guild

* add more guilds to those that do not have the bannedWords blocker applied

* fix lint errors
2017-11-16 19:52:17 +01:00
SabreCat
67b16d91a3 fix(drawer): remove overflow CSS 2017-11-16 16:38:38 +00:00
Alys
8d444980de remove unneeded space from in front of a full stop 2017-11-16 19:29:38 +10:00
MathWhiz
08ccd595f2 Add quantity information when purchasing items (#9481) 2017-11-16 10:08:11 +01:00
Alys
6b625a60ab remove client-side purchase of Armoire - fixes #9432 (#9463) 2017-11-16 10:06:45 +01:00
Matteo Pagliazzi
6bdb695616 Merge branch 'CSE2410-TeamZero-issue9334' into develop 2017-11-15 17:47:27 +01:00
Matteo Pagliazzi
b999d46142 Merge branch 'issue9334' of https://github.com/CSE2410-TeamZero/habitica into CSE2410-TeamZero-issue9334 2017-11-15 17:47:17 +01:00
Matteo Pagliazzi
ce0f5af08d update package-lock.json 2017-11-15 17:44:55 +01:00
Allister
c5296d4cb0 Add variable placeholder. (#9465)
Changes the character translation string `charactersRemaining`  in
groups.json to include a variable placeholder `<%= characters %>`
2017-11-15 17:41:18 +01:00
MathWhiz
22b683b1d9 Fix regex for chat highlighting (#9411)
* Fix regex for chat highlighting

* Fix lint errors
2017-11-15 17:39:29 +01:00
Joseti
229fd06ee3 Removed "Tag list in tasks starts collapsed" settings and strings (#9406) 2017-11-15 17:37:15 +01:00
Stephanie Wu
eb8f84aae0 #9354 Indicate that Orb of Rebirth is instant (#9357)
* #9354 Indicate that Orb of Rebirth is instant

Clarified the text

* #9354 Indicate that Orb of Rebirth is instant

Reverted changes in all but locales/en
2017-11-15 17:35:51 +01:00
Andrew
69b69e9d27 Update appFooter.vue in attempt to fix #9336 (#9348)
* Update appFooter.vue in attempt to fix #9336

Make whole social circle clickable.
Replace div with `a` tag allowing whole circle to become a link.

* Indent and reformat "Not ready yet" Instagram button.
2017-11-15 17:35:16 +01:00
dnlup
8033e7c0a0 Fix Incorrect Heading margins (#9311)
* fix(style): Fix margin of headers in inventory page

Set the `mb-0` class in h1 header to `mb-4`.
Set the class if `h2` header to `mb-3`.

* fix(style): Fix margin of headers in shops section

Set the `mb-0` class in h1 header to `mb-4`.
Set the class of `h2` header to `mb-3`.

* fix(style): Fix margin of headers in shops adn time traveler section

Set the `mb-0` class in h1 header to `mb-4`.
Set the class of `h2` header to `mb-3`.
2017-11-15 17:34:57 +01:00
Emily Ong
e39b80bb9a changed to 'createChallengeCloneTasks' (#9300)
* changed to 'createChallengeCloneTasks'

* createChallengeCloneTasks

* Added new i18n strings
2017-11-15 17:34:33 +01:00
Ryan Holinshead
2038d5e7c8 Choose class modal remove tooltip (#9298)
* Change chooseClass modal opt out button and tooltip
- Remove hover tooltip from opt out
- Fix centering of choose class button
- Add some margin between opt out and choose class buttons
- Make cursor on opt out text the pointer to make it obvious it's clickable

* Alphabetical order class selector
2017-11-15 17:32:49 +01:00
Aquib Master
46a8ee52d4 Modify relative dueIn time on tasks to be in days (#9251)
* Modify relative dueIn time on tasks to be in days

- Normalizes the current time and task due time to the ends of their respective days.
- Returns 'today' if the dates are the same day else uses moment's humanize function to allow for weeks, months, years and so on.

* Modify task due date to appear grey when due the next day
2017-11-15 17:32:30 +01:00
William Perry
52064f6b2a Changes animal attribute that A-Z sort is applied to. (#9241) 2017-11-15 17:31:10 +01:00
Łukasz Dobrogowski
f15a27a7f1 added favicons to the new client (#9235) 2017-11-15 17:29:58 +01:00
Blade Barringer
fcf0dd87f9 Rename constant for restricted email domains (#9459) 2017-11-15 17:14:13 +01:00
Matteo Pagliazzi
ab974675b9 More Tasks page fixes (#9475)
* fix datepicker not closing when clicking outside of it, fixes #9346

* fixes #9441
2017-11-15 17:06:32 +01:00
Sabe Jones
cb612d99d7 chore(sprites): recompile 2017-11-15 01:31:01 +00:00
Unknown
cb5a47ec7b Deleted unnessecary import. 2017-11-14 20:18:36 -05:00
Sabe Jones
838b9a5822 Merge branch 'release' into develop 2017-11-15 01:03:07 +00:00
Sabe Jones
6c65056e2b 4.11.0 2017-11-15 01:01:18 +00:00
Sabe Jones
2bf0fdf4a2 chore(i18n): update locales 2017-11-15 01:00:56 +00:00
SabreCat
cd5ff04ee4 chore(sprites): compile 2017-11-15 00:53:27 +00:00
SabreCat
dbc5b9f850 feat(content): Yarn Pet Quest 2017-11-15 00:53:10 +00:00
Unknown
ca3437d676 Added pin.scss to index.scss. 2017-11-14 19:26:11 -05:00
Keith Holliday
c43ca62bc4 Added check for balance with respect to quantity (#9469) 2017-11-14 16:55:08 -07:00
Keith Holliday
eaa91b2a09 Group plan fixes (#9437)
* Prevented title editing on personal page

* Fixed claim/unlclaim from user task page

* Removed task from local on delete

* Immediately show unassigned bar

* Add move to group tasks

* Fixed group member count increase

* Added upgrade when group plan is canceled
2017-11-14 16:54:11 -07:00
Unknown
6259b68b4f Does not reuse same css classes, imports them from pin.scss. For the togglePin function the notification. 2017-11-14 18:10:33 -05:00
Unknown
08073acf11 Solved Issue #9334. Added unpin feature for rewards list in tasks. 2017-11-14 12:24:11 -05:00
Matteo Pagliazzi
bddafd4392 fix bailey css 2017-11-13 12:12:41 +01:00
Matteo Pagliazzi
3ab14e4e5a fix checklist deleting 2017-11-13 12:08:42 +01:00
Matteo Pagliazzi
d9830950aa fix modal handling 2017-11-10 12:43:50 +01:00
Matteo Pagliazzi
45696a6273 remove outline from focused inputs 2017-11-10 12:02:20 +01:00
Sabe Jones
3f92317b9e Revert "Implements repeat every X days since last completion (Fixes #6941) (#8962)"
This reverts commit 9d69d4b863.
2017-11-09 22:13:34 +00:00
Sabe Jones
848883736d 4.10.1 2017-11-09 21:23:55 +00:00
Alys
69e0ab11c0 swap achievement badge icons for Challenges Won and Quests 2017-11-10 07:10:03 +10:00
Matteo Pagliazzi
d8d7a81edf Tasks: fixes and new edit task design (#9442)
* add missing tooltips

* makes sure due date is update correctly, fixes #9436

* do not collapse checklist when casting spells, fixes #9345

* start to fix spells drawer

* fix drawer requiring two clicks to open
2017-11-09 19:38:48 +01:00
Matteo Pagliazzi
03a09b7546 Fix issues with Bootstrap Vue and Boostrap upgrades (#9452)
* start to fix modals

* fixed cards paddings

* fix notifications not being marked as read

* add tests for reading a notification

* fixed indentation and added tests for reading multiple notifications

* register from home page using enter key
2017-11-09 19:37:47 +01:00
Sabe Jones
0d2737572d Merge branch 'release' into develop 2017-11-09 17:57:48 +00:00
Sabe Jones
28149202db chore(i18n): update locales 2017-11-09 17:53:06 +00:00
Alys
4b23cd9f23 add missing string for dateEndNovember 2017-11-09 17:46:12 +00:00
Alys
8aaabdc086 add missing removeInvite string 2017-11-09 17:45:59 +00:00
SabreCat
6e6ca05352 fix(sprites): missing Guild image 2017-11-09 17:44:48 +00:00
SabreCat
74d8ecc732 Merge branch 'release' into develop 2017-11-09 17:24:48 +00:00
Alys
63c8a09e22 add missing string for dateEndNovember 2017-11-09 21:58:16 +10:00
Alys
865f623c99 add missing removeInvite string 2017-11-09 18:16:58 +10:00
Matteo Pagliazzi
061968dd1a intro tour: fix justin position 2017-11-08 19:55:28 +01:00
Matteo Pagliazzi
21bc91c3ae chore(npm): upgrade bootstrap-vue 2017-11-08 18:56:28 +01:00
Matteo Pagliazzi
0bfc6608c1 update package-lock.json 2017-11-08 18:44:18 +01:00
negue
4108a22d78 [WIP] bootstrap-vue upgrade (#9178)
* update bootstrap-vue to 1.0.0-beta.9 - remove all individual bootstrap components and use BootstrapVue into Vue

* change modal action names from show::modal to bv::show::modal

* check if drops are undefined

* fix modal widths - sellModal now using input instead of dropbox

* upgrade to bootstrap 4.0beta

* include package-lock changes

* fix app menu dropdown position

* upgrade bootstrap to beta2 (was missing grid offset and other fixes) - refix header menu position

* fix tags popup (auto width to max not working) - fix filter panel width (adding width: 100% works until max-width)

* show hide logo on different screensize (new css breakpoints - http://getbootstrap.com/docs/4.0/utilities/display/ )

* fix package-lock?

* fix active button style / app header toggle button

* fix package-lock !

* update package lock after merge - new mixin "openedItemRows" to save the "show more/show less" in stable

* mixin naming style

* fix buyQuestModal marginTop

* fix customMenuDropdown position

* fix userDropdown items
2017-11-08 18:40:37 +01:00
Matteo Pagliazzi
34f6b63968 remove unused loggin for ios purchases 2017-11-08 16:12:11 +01:00
Sabe Jones
8d1ebff7e9 4.10.0 2017-11-07 23:55:16 +00:00
Sabe Jones
993df72708 chore(i18n): update locales 2017-11-07 23:53:47 +00:00
SabreCat
50d3226a86 feat(content): change to Thunderstorm Hatching Potions 2017-11-07 22:02:46 +00:00
Keith Holliday
17ce2febf9 [WIP] Add initial fixes for concurrency (#9321)
* Add initial fixes for concurrency

* Added memory edit for notifications

* Fixed tag delete

* Fixed adding and moving task order

* Updated delete task

* Fixed lint

* Fixed task adding

* Switch to mongoose push and pull
2017-11-07 13:19:39 -07:00
Matteo Pagliazzi
0caa195c6f fix confetti image on home page 2017-11-07 20:34:52 +01:00
Keith Holliday
f964e3c0a5 Updated avatar menu icons and style (#9409) 2017-11-07 11:57:42 -07:00
Asif Mallik
9d69d4b863 Implements repeat every X days since last completion (Fixes #6941) (#8962)
* Implemented repeat after completion

* Added tests for repeat after completion in shouldDo.test.js

* Remove lastTicked

* Undoes removal of website/client/README.md
2017-11-07 12:56:46 -06:00
Matteo Pagliazzi
19500600bc cache sprites and fix images caching (#9422) 2017-11-07 11:48:23 +01:00
Matteo Pagliazzi
f25fe9e263 remove forked vue version 2017-11-06 23:46:18 +00:00
Matteo Pagliazzi
5f37487c23 Fix guilds fetching (#9416)
* fix guilds fetching

* fix missing categories
2017-11-06 17:16:41 +01:00
Alys
12fd79059b adjust login messages to indicate case-sensitivity and hint about google sign-in 2017-11-04 20:34:17 +10:00
Alys
232061a629 improve wording for Brutal Smash skill 2017-11-04 20:24:35 +10:00
Alys
3fcc1c522d allow "visit the stable" text to be translated 2017-11-04 20:16:47 +10:00
Matteo Pagliazzi
8302c50302 remove forked vue version 2017-11-04 10:24:45 +01:00
Sabe Jones
6eb06fb054 4.9.1 2017-11-03 22:58:12 +00:00
Keith Holliday
286c8c7530 Ensured sort value is true for checklists (#9386) 2017-11-03 15:50:11 -05:00
Keith Holliday
47ab8f2073 Added exitence checks (#9383) 2017-11-03 15:38:05 -05:00
Sabe Jones
83353f6481 Merge branch 'paglias/fix-sprites' into release 2017-11-03 20:35:20 +00:00
Matteo Pagliazzi
9cbd7ad62d fix customize-options sprites 2017-11-03 19:06:06 +01:00
thehollidayinn
3485a1d0bc Reloaded completed todos if we are unlinking a todo 2017-11-03 10:33:20 -06:00
thehollidayinn
2e5106fda1 Removed old state item 2017-11-03 10:24:46 -06:00
thehollidayinn
2e5f5714e4 Added broken task event 2017-11-03 10:24:00 -06:00
Sabe Jones
3cf7b2c96c 4.9.0 2017-11-03 03:04:52 +00:00
Sabe Jones
286db39478 chore(i18n): update locales 2017-11-03 03:04:15 +00:00
SabreCat
4d4c1cfaf3 chore(sprites): compile 2017-11-03 02:54:26 +00:00
SabreCat
d7ad3efabf fix(news): proper Take This announcement 2017-11-03 02:49:41 +00:00
SabreCat
f8876fe055 feat(content): Backgrounds and Armoire 2017-11
End Habitoween and Fall Festival
2017-11-03 01:56:15 +00:00
Keith Holliday
b973335d69 Added extra months to account for months with larger amount of days (#9379) 2017-11-02 15:44:24 -06:00
Phillip Thelen
3b6fce0708 Add "stats" as a tutorial step (#9377)
This is used mostly for the mobile apps to identify if the tutorial step about stats has be shown yet or not.
2017-11-02 15:57:16 -05:00
Keith Holliday
ff6bd6de71 Ensured user selects plan (#9378) 2017-11-02 14:26:00 -06:00
Keith Holliday
042afe1df3 Add close to tags popup (#9376) 2017-11-02 14:25:37 -06:00
Matteo Pagliazzi
a208ba4aba Tasks v2 Part 2 (#9236)
* start updating colors for tasks controls

* finish updating controls colors

* change hoevr behavior

* change transition duration

* update color with transition

* refactor menu wip

* wip

* upgrade vue deps

* fix warnings

* fix menu

* misc fixes

* more fixes

* fix badge

* fix margins in menu

* wip tooltips

* tooltips

* fix checklist colors

* add badges

* fix quick add input

* add transition to task controls too

* add batch add

* fix task filtering

* finish tasks badges

* fix menu

* upgrade deps

* fix shop items using all the same image

* fix animation

* disable client tests until we remove phantomjs

* revert changes to tasks colors

* fix opacity in task modal inputs

* remove client unit tests from travis

* wip task dropdown

* fix z-index for task footer/header

* fixes and add files

* fixes

* bigger clickable area

* more space to open task dropdown

* droddown position

* fix menu position

* make sure other dropdowns get closed correctly

* fixes

* start to fix z-index

* draggable = false for task dropdown

* fix for dropdown position

* implement move to top / bottom

* fix push to bottom

* typo

* fix drag and drop

* use standard code

* wider click area for dropdown

* unify badge look

* fix padding

* misc fixes

* more fixes

* make dropdown scrollable

* misc fixes

* fix padding for notes

* use existing code instead of new props
2017-11-02 21:07:38 +01:00
Keith Holliday
0e958fd306 Prevented challenge edit during RYA (#9373) 2017-11-02 11:37:29 -06:00
Keith Holliday
d98fe79e9c Fixed member modal search (#9375) 2017-11-02 11:36:08 -06:00
Keith Holliday
0e5a811b98 Prevented challenge prize edit. Fixed edit to create modal (#9374) 2017-11-02 11:30:01 -06:00
Keith Holliday
a28aea65f8 Added reward text (#9332) 2017-11-01 10:03:23 -06:00
Keith Holliday
0f92349902 Added fix for multiple level up (#9330) 2017-11-01 10:01:58 -06:00
Sabe Jones
d4881cb73a Merge branch 'release' into develop 2017-10-31 19:22:58 +00:00
Sabe Jones
b3216fdb85 4.8.0 2017-10-31 19:22:27 +00:00
Keith Holliday
3e37941e0a Bulk stats (#9260)
* Reorganized stats

* Organized allocation common code

* Added bulk allocate to common

* Added allocate bulk route

* Fixed structure and lint

* Fixed import and apidoc
2017-10-31 12:57:44 -06:00
Alys
32088767ac update github templates for new website and wiki page locations 2017-10-31 21:25:01 +10:00
Alys
f4d021ab8c fix an inaccurate comment about guilds needing summaries 2017-10-31 20:54:23 +10:00
SabreCat
8532203717 Merge branch 'release' into develop 2017-10-31 01:03:07 +00:00
SabreCat
365daba6fc 4.7.1 2017-10-31 00:49:58 +00:00
SabreCat
70692752c7 fix(sprites): bad shirt centering 2017-10-31 00:48:37 +00:00
Sabe Jones
95d4016678 Merge branch 'release' into develop 2017-10-30 20:50:32 +00:00
Sabe Jones
061457b268 4.7.0 2017-10-30 20:50:11 +00:00
Sabe Jones
e7ec9a6d65 chore(i18n): update locales 2017-10-30 20:49:09 +00:00
SabreCat
d1396e7bc6 chore(sprites): compile 2017-10-30 20:41:32 +00:00
SabreCat
d5cedaa925 feat(event): Habitoween 2017-10-30 20:40:29 +00:00
Sabe Jones
bea813b318 Merge branch 'release' into develop 2017-10-26 22:47:18 +00:00
Sabe Jones
b31268fbc2 4.6.3 2017-10-26 22:46:51 +00:00
thehollidayinn
35727228f0 Removed autorefresh 2017-10-26 17:20:52 -05:00
SabreCat
feb7ab8345 Merge branch 'release' into develop 2017-10-26 21:16:09 +00:00
SabreCat
f4422b8d6c 4.6.2 2017-10-26 21:14:13 +00:00
SabreCat
2d4dc9e23c chore(news): blog Bailey 2017-10-26 21:13:04 +00:00
Tyler Nychka
bba2e71af3 Task order fix (#8928) 2017-10-26 10:37:51 -05:00
Mel
26123ac6ae API - Get challenges for a group does not allow party or habitrpg (#8882)
* test get party challenges by party ID

* tavern challenge tests; failing tests with ID 'party' or 'habitrpg'

* allow finding challenges by groupid 'party' or 'habitrpg'

* use single quotes in strings
2017-10-25 15:35:23 -05:00
Zobdek
addee73e4d Set _cronSignature to current time instead of uuid (#8565)
* Changes made to satisfy #8163. _cronSignature is set to current time when cron starts so that if cron fails to set _cronSignature to 'NOT_RUNNING' for any reason a new cron can be started after a set amount of time (1 hour for now)

* fix lint errors

* changed cronTimeout to CRON_TIMEOUT

* Changed variable names and comments to be more clear

* Fixed stub for failing test so that it matches new mongo db update call signature

* First pass at unit tests, error messages and some other things need to be determined

* Fixed a tab that snuck in :/

* Fixed lint issues (issues with spaces)

* Fix infix operator spacing

* Created constant. Make sure cron failure test verifies that it is failing for the right reason

* Fixed lint errors

* Removed no longer used uuid import
2017-10-25 15:29:16 -05:00
Keith Holliday
c39505d41c Hotfix/add autoreload (#9269)
* Added auto refresh on server update back

* Fixed lint

* Updated copy
2017-10-25 15:22:26 -05:00
Lachlan Heywood
9736ef0d25 Add ESLint to gulp scripts (#9259)
* Remove gulp/* and gulpfile from ESLint ignores

* Update .eslintrc in local gulp folder

* Start work on refactoring gulp files

* add radix

* Add line-specific eslint exceptions

* removed redundant eslint file for gulp

* add more exceptions

* Add exceptions to main gulpfile.js
2017-10-25 10:45:03 +02:00
Sabe Jones
638259b885 Merge branch 'release' into develop 2017-10-24 22:42:20 +00:00
Sabe Jones
c24545cae5 4.6.1 2017-10-24 22:41:58 +00:00
Keith Holliday
6bc70ca471 Hotfix/oct 24 fixes (#9261)
* Removed max height from text section

* Added items to mystery sets

* Group manager fixes

* Fixed promo code apply
2017-10-24 17:41:36 -05:00
Sabe Jones
d2f0d7b20b Merge branch 'release' into develop 2017-10-24 20:36:24 +00:00
Lula Villalobos
3c9f7ff9d8 Bug Fix on ScoreTask: Gp added instead of append [fixes #9180] (#9207)
* added parseInt to stats.gp so it can add the new value

* added radix parameter to fix lint issue

* revert changes in scoreTask.js

* convert restoreValues into numbers before setting to users.stats
2017-10-24 16:23:12 +02:00
1038 changed files with 41041 additions and 36573 deletions

View File

@@ -6,6 +6,9 @@ website/transpiled-babel/
website/common/transpiled-babel/
dist/
dist-client/
apidoc_build/
content_cache/
node_modules/
# Not linted
website/client-old/
@@ -16,5 +19,3 @@ migrations/*
scripts/*
website/common/browserify.js
Gruntfile.js
gulpfile.js
gulp

View File

@@ -4,7 +4,7 @@
# Pull Request
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Habitica_Git#Pull_Request)
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API)
# Requesting a feature

View File

@@ -6,7 +6,7 @@
[//]: # (For more guidelines see https://github.com/HabitRPG/habitica/issues/2760)
[//]: # (Fill out relevant information - UUID is found in Settings -> API)
[//]: # (Fill out relevant information - UUID is found from the Habitia website at User Icon > Settings > API)
### General Info
* UUID:
* Browser:

View File

@@ -1,4 +1,4 @@
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Habitica_Git#Pull_Request for more info)
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API for more info)
[//]: # (Put Issue # or URL here, if applicable. This will automatically close the issue if your PR is merged in)
Fixes put_issue_url_here
@@ -8,7 +8,7 @@ Fixes put_issue_url_here
[//]: # (Put User ID in here - found in Settings -> API)
[//]: # (Put User ID in here - found on the Habitica website at User Icon > Settings > API)
----
UUID:

View File

@@ -34,5 +34,4 @@ env:
- TEST="test:sanity"
- TEST="test:content" COVERAGE=true
- TEST="test:common" COVERAGE=true
- TEST="client:unit" COVERAGE=true
- TEST="apidoc"

View File

@@ -1,5 +1,16 @@
FROM node:boron
ENV ADMIN_EMAIL admin@habitica.com
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
ENV AMAZON_PAYMENTS_SELLER_ID AMQ3SB4SG5E91
ENV AMPLITUDE_KEY e8d4c24b3d6ef3ee73eeba715023dd43
ENV BASE_URL https://habitica.com
ENV FACEBOOK_KEY 128307497299777
ENV GA_ID UA-33510635-1
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
ENV NODE_ENV production
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
# Upgrade NPM to v5 (Yarn is needed because of this bug https://github.com/npm/npm/issues/16807)
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
RUN yarn global add npm@5
@@ -9,7 +20,7 @@ RUN npm install -g gulp mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone --branch v4.0.3 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN git clone --branch v4.11.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN gulp build:prod --force

View File

@@ -1,10 +0,0 @@
{
"root": true,
"env": {
"node": true,
},
"extends": [
"habitrpg/server",
"habitrpg/babel"
],
}

View File

@@ -22,5 +22,5 @@ gulp.task('apidoc', ['apidoc:clean'], (done) => {
});
gulp.task('apidoc:watch', ['apidoc'], () => {
return gulp.watch(APIDOC_SRC_PATH + '/**/*.js', ['apidoc']);
return gulp.watch(`${APIDOC_SRC_PATH}/**/*.js`, ['apidoc']);
});

View File

@@ -1,36 +0,0 @@
import gulp from 'gulp';
import fs from 'fs';
// Copy Bootstrap 4 config variables from /website /node_modules so we can check
// them into Git
const BOOSTRAP_NEW_CONFIG_PATH = 'website/client/assets/scss/bootstrap_config.scss';
const BOOTSTRAP_ORIGINAL_CONFIG_PATH = 'node_modules/bootstrap/scss/_custom.scss';
// https://stackoverflow.com/a/14387791/969528
function copyFile(source, target, cb) {
let cbCalled = false;
function done(err) {
if (!cbCalled) {
cb(err);
cbCalled = true;
}
}
let rd = fs.createReadStream(source);
rd.on('error', done);
let wr = fs.createWriteStream(target);
wr.on('error', done);
wr.on('close', () => done());
rd.pipe(wr);
}
gulp.task('bootstrap', (done) => {
// use new config
copyFile(
BOOSTRAP_NEW_CONFIG_PATH,
BOOTSTRAP_ORIGINAL_CONFIG_PATH,
done,
);
});

View File

@@ -1,10 +1,9 @@
import gulp from 'gulp';
import runSequence from 'run-sequence';
import babel from 'gulp-babel';
import webpackProductionBuild from '../webpack/build';
gulp.task('build', () => {
if (process.env.NODE_ENV === 'production') {
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
gulp.start('build:prod');
}
});
@@ -24,15 +23,15 @@ gulp.task('build:common', () => {
gulp.task('build:server', ['build:src', 'build:common']);
// Client Production Build
gulp.task('build:client', ['bootstrap'], (done) => {
gulp.task('build:client', (done) => {
webpackProductionBuild((err, output) => {
if (err) return done(err);
console.log(output);
console.log(output); // eslint-disable-line no-console
});
});
gulp.task('build:prod', [
'build:server',
'build:server',
'build:client',
'apidoc',
]);

View File

@@ -7,10 +7,11 @@ import gulp from 'gulp';
// Add additional properties to the repl's context
let improveRepl = (context) => {
// Let "exit" and "quit" terminate the console
['exit', 'quit'].forEach((term) => {
Object.defineProperty(context, term, { get () { process.exit(); }});
Object.defineProperty(context, term, { get () {
process.exit();
}});
});
// "clear" clears the screen
@@ -18,12 +19,12 @@ let improveRepl = (context) => {
process.stdout.write('\u001B[2J\u001B[0;0f');
}});
context.Challenge = require('../website/server/models/challenge').model;
context.Group = require('../website/server/models/group').model;
context.User = require('../website/server/models/user').model;
context.Challenge = require('../website/server/models/challenge').model; // eslint-disable-line global-require
context.Group = require('../website/server/models/group').model; // eslint-disable-line global-require
context.User = require('../website/server/models/user').model; // eslint-disable-line global-require
var isProd = nconf.get('NODE_ENV') === 'production';
var mongooseOptions = !isProd ? {} : {
const isProd = nconf.get('NODE_ENV') === 'production';
const mongooseOptions = !isProd ? {} : {
replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
};
@@ -31,16 +32,15 @@ let improveRepl = (context) => {
mongoose.connect(
nconf.get('NODE_DB_URI'),
mongooseOptions,
function (err) {
(err) => {
if (err) throw err;
logger.info('Connected with Mongoose');
}
)
);
};
gulp.task('console', (cb) => {
gulp.task('console', () => {
improveRepl(repl.start({
prompt: 'Habitica > ',
}).context);

View File

@@ -11,78 +11,38 @@ import {each} from 'lodash';
// https://github.com/Ensighten/grunt-spritesmith/issues/67#issuecomment-34786248
const MAX_SPRITESHEET_SIZE = 1024 * 1024 * 3;
const IMG_DIST_PATH = 'website/static/sprites/';
const IMG_DIST_PATH = 'website/client/assets/images/sprites/';
const CSS_DIST_PATH = 'website/client/assets/css/sprites/';
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
function checkForSpecialTreatment (name) {
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
return name.match(regex) || name === 'head_0';
}
gulp.task('sprites:main', () => {
let mainSrc = sync('website/raw_sprites/spritesmith/**/*.png');
return createSpritesStream('main', mainSrc);
});
function calculateImgDimensions (img, addPadding) {
let dims = sizeOf(img);
gulp.task('sprites:largeSprites', () => {
let largeSrc = sync('website/raw_sprites/spritesmith_large/**/*.png');
return createSpritesStream('largeSprites', largeSrc);
});
gulp.task('sprites:clean', (done) => {
clean(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`, done);
});
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
console.log('Verifiying that images do not exceed max dimensions');
let numberOfSheetsThatAreTooBig = 0;
let distSpritesheets = sync(`${IMG_DIST_PATH}*.png`);
each(distSpritesheets, (img, index) => {
let spriteSize = calculateImgDimensions(img);
if (spriteSize > MAX_SPRITESHEET_SIZE) {
numberOfSheetsThatAreTooBig++;
let name = basename(img, '.png');
console.error(`WARNING: ${name} might be too big - ${spriteSize} > ${MAX_SPRITESHEET_SIZE}`);
}
});
if (numberOfSheetsThatAreTooBig > 0) {
console.error(`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle them, but there is a margin of error in these calculations so it is probably okay. Mention this to an admin so they can test a staging site on mobile Safari after your PR is merged.`); // https://github.com/HabitRPG/habitica/pull/6683#issuecomment-185462180
} else {
console.log('All images are within the correct dimensions');
let requiresSpecialTreatment = checkForSpecialTreatment(img);
if (requiresSpecialTreatment) {
let newWidth = dims.width < 90 ? 90 : dims.width;
let newHeight = dims.height < 90 ? 90 : dims.height;
dims = {
width: newWidth,
height: newHeight,
};
}
});
function createSpritesStream (name, src) {
let spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src);
let stream = mergeStream();
let padding = 0;
each(spritesheetSliceIndicies, (start, index) => {
let slicedSrc = src.slice(start, spritesheetSliceIndicies[index + 1]);
if (addPadding) {
padding = dims.width * 8 + dims.height * 8;
}
let spriteData = gulp.src(slicedSrc)
.pipe(spritesmith({
imgName: `spritesmith-${name}-${index}.png`,
cssName: `spritesmith-${name}-${index}.css`,
algorithm: 'binary-tree',
padding: 1,
cssTemplate: 'website/raw_sprites/css/css.template.handlebars',
cssVarMap: cssVarMap,
}));
if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims); // eslint-disable-line no-console
let imgStream = spriteData.img
.pipe(imagemin())
.pipe(gulp.dest(IMG_DIST_PATH));
let totalPixelSize = dims.width * dims.height + padding;
let cssStream = spriteData.css
.pipe(gulp.dest(CSS_DIST_PATH));
stream.add(imgStream);
stream.add(cssStream);
});
return stream;
return totalPixelSize;
}
function calculateSpritesheetsSrcIndicies (src) {
@@ -102,37 +62,6 @@ function calculateSpritesheetsSrcIndicies (src) {
return slices;
}
function calculateImgDimensions (img, addPadding) {
let dims = sizeOf(img);
let requiresSpecialTreatment = checkForSpecialTreatment(img);
if (requiresSpecialTreatment) {
let newWidth = dims.width < 90 ? 90 : dims.width;
let newHeight = dims.height < 90 ? 90 : dims.height;
dims = {
width: newWidth,
height: newHeight,
};
}
let padding = 0;
if (addPadding) {
padding = (dims.width * 8) + (dims.height * 8);
}
if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims);
let totalPixelSize = (dims.width * dims.height) + padding;
return totalPixelSize;
}
function checkForSpecialTreatment (name) {
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
return name.match(regex) || name === 'head_0';
}
function cssVarMap (sprite) {
// For hair, skins, beards, etc. we want to output a '.customize-options.WHATEVER' class, which works as a
// 60x60 image pointing at the proper part of the 90x90 sprite.
@@ -141,18 +70,93 @@ function cssVarMap (sprite) {
if (requiresSpecialTreatment) {
sprite.custom = {
px: {
offset_x: `-${ sprite.x + 25 }px`,
offset_y: `-${ sprite.y + 15 }px`,
offsetX: `-${ sprite.x + 25 }px`,
offsetY: `-${ sprite.y + 15 }px`,
width: '60px',
height: '60px',
},
};
}
if (~sprite.name.indexOf('shirt'))
sprite.custom.px.offset_y = `-${ sprite.y + 30 }px`; // even more for shirts
if (~sprite.name.indexOf('hair_base')) {
let styleArray = sprite.name.split('_').slice(2,3);
if (sprite.name.indexOf('shirt') !== -1)
sprite.custom.px.offsetY = `-${ sprite.y + 35 }px`; // even more for shirts
if (sprite.name.indexOf('hair_base') !== -1) {
let styleArray = sprite.name.split('_').slice(2, 3);
if (Number(styleArray[0]) > 14)
sprite.custom.px.offset_y = `-${ sprite.y }px`; // don't crop updos
sprite.custom.px.offsetY = `-${ sprite.y }px`; // don't crop updos
}
}
function createSpritesStream (name, src) {
let spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src);
let stream = mergeStream();
each(spritesheetSliceIndicies, (start, index) => {
let slicedSrc = src.slice(start, spritesheetSliceIndicies[index + 1]);
let spriteData = gulp.src(slicedSrc)
.pipe(spritesmith({
imgName: `spritesmith-${name}-${index}.png`,
cssName: `spritesmith-${name}-${index}.css`,
algorithm: 'binary-tree',
padding: 1,
cssTemplate: 'website/raw_sprites/css/css.template.handlebars',
cssVarMap,
}));
let imgStream = spriteData.img
.pipe(imagemin())
.pipe(gulp.dest(IMG_DIST_PATH));
let cssStream = spriteData.css
.pipe(gulp.dest(CSS_DIST_PATH));
stream.add(imgStream);
stream.add(cssStream);
});
return stream;
}
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
gulp.task('sprites:main', () => {
let mainSrc = sync('website/raw_sprites/spritesmith/**/*.png');
return createSpritesStream('main', mainSrc);
});
gulp.task('sprites:largeSprites', () => {
let largeSrc = sync('website/raw_sprites/spritesmith_large/**/*.png');
return createSpritesStream('largeSprites', largeSrc);
});
gulp.task('sprites:clean', (done) => {
clean(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`, done);
});
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
console.log('Verifiying that images do not exceed max dimensions'); // eslint-disable-line no-console
let numberOfSheetsThatAreTooBig = 0;
let distSpritesheets = sync(`${IMG_DIST_PATH}*.png`);
each(distSpritesheets, (img) => {
let spriteSize = calculateImgDimensions(img);
if (spriteSize > MAX_SPRITESHEET_SIZE) {
numberOfSheetsThatAreTooBig++;
let name = basename(img, '.png');
console.error(`WARNING: ${name} might be too big - ${spriteSize} > ${MAX_SPRITESHEET_SIZE}`); // eslint-disable-line no-console
}
});
if (numberOfSheetsThatAreTooBig > 0) {
// https://github.com/HabitRPG/habitica/pull/6683#issuecomment-185462180
console.error( // eslint-disable-line no-console
`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle
them, but there is a margin of error in these calculations so it is probably okay. Mention
this to an admin so they can test a staging site on mobile Safari after your PR is merged.`);
} else {
console.log('All images are within the correct dimensions'); // eslint-disable-line no-console
}
});

View File

@@ -1,21 +1,12 @@
import {
pipe,
awaitPort,
kill,
runMochaTests,
} from './taskHelper';
import { server as karma } from 'karma';
import mongoose from 'mongoose';
import { exec } from 'child_process';
import psTree from 'ps-tree';
import gulp from 'gulp';
import Bluebird from 'bluebird';
import runSequence from 'run-sequence';
import os from 'os';
import nconf from 'nconf';
import fs from 'fs';
const i18n = require('../website/server/libs/i18n');
// TODO rewrite
@@ -24,7 +15,6 @@ let server;
const TEST_DB_URI = nconf.get('TEST_DB_URI');
const API_V3_TEST_COMMAND = 'npm run test:api-v3';
const SANITY_TEST_COMMAND = 'npm run test:sanity';
const COMMON_TEST_COMMAND = 'npm run test:common';
const CONTENT_TEST_COMMAND = 'npm run test:content';
@@ -34,14 +24,14 @@ const CONTENT_OPTIONS = {maxBuffer: 1024 * 500};
let testResults = [];
let testCount = (stdout, regexp) => {
let match = stdout.match(regexp);
return parseInt(match && match[1] || 0);
return parseInt(match && match[1] || 0, 10);
};
let testBin = (string, additionalEnvVariables = '') => {
if (os.platform() === 'win32') {
if (additionalEnvVariables != '') {
if (additionalEnvVariables !== '') {
additionalEnvVariables = additionalEnvVariables.split(' ').join('&&set ');
additionalEnvVariables = 'set ' + additionalEnvVariables + '&&';
additionalEnvVariables = `set ${additionalEnvVariables}&&`;
}
return `set NODE_ENV=test&&${additionalEnvVariables}${string}`;
} else {
@@ -49,9 +39,9 @@ let testBin = (string, additionalEnvVariables = '') => {
}
};
gulp.task('test:nodemon', (done) => {
process.env.PORT = TEST_SERVER_PORT;
process.env.NODE_DB_URI = TEST_DB_URI;
gulp.task('test:nodemon', () => {
process.env.PORT = TEST_SERVER_PORT; // eslint-disable-line no-process-env
process.env.NODE_DB_URI = TEST_DB_URI; // eslint-disable-line no-process-env
runSequence('nodemon');
});
@@ -68,8 +58,12 @@ gulp.task('test:prepare:mongo', (cb) => {
gulp.task('test:prepare:server', ['test:prepare:mongo'], () => {
if (!server) {
server = exec(testBin('node ./website/server/index.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => {
if (error) { throw `Problem with the server: ${error}`; }
if (stderr) { console.error(stderr); }
if (error) {
throw new Error(`Problem with the server: ${error}`);
}
if (stderr) {
console.error(stderr); // eslint-disable-line no-console
}
});
}
});
@@ -84,7 +78,7 @@ gulp.task('test:prepare', [
gulp.task('test:sanity', (cb) => {
let runner = exec(
testBin(SANITY_TEST_COMMAND),
(err, stdout, stderr) => {
(err) => {
if (err) {
process.exit(1);
}
@@ -97,7 +91,7 @@ gulp.task('test:sanity', (cb) => {
gulp.task('test:common', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(COMMON_TEST_COMMAND),
(err, stdout, stderr) => {
(err) => {
if (err) {
process.exit(1);
}
@@ -118,7 +112,7 @@ gulp.task('test:common:watch', ['test:common:clean'], () => {
gulp.task('test:common:safe', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(COMMON_TEST_COMMAND),
(err, stdout, stderr) => {
(err, stdout) => { // eslint-disable-line handle-callback-err
testResults.push({
suite: 'Common Specs\t',
pass: testCount(stdout, /(\d+) passing/),
@@ -135,7 +129,7 @@ gulp.task('test:content', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(CONTENT_TEST_COMMAND),
CONTENT_OPTIONS,
(err, stdout, stderr) => {
(err) => {
if (err) {
process.exit(1);
}
@@ -157,7 +151,7 @@ gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
let runner = exec(
testBin(CONTENT_TEST_COMMAND),
CONTENT_OPTIONS,
(err, stdout, stderr) => {
(err, stdout) => { // eslint-disable-line handle-callback-err
testResults.push({
suite: 'Content Specs\t',
pass: testCount(stdout, /(\d+) passing/),
@@ -173,7 +167,7 @@ gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
gulp.task('test:api-v3:unit', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-unit --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/unit --recursive --require ./test/helpers/start-server'),
(err, stdout, stderr) => {
(err) => {
if (err) {
process.exit(1);
}
@@ -192,7 +186,7 @@ gulp.task('test:api-v3:integration', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
{maxBuffer: 500 * 1024},
(err, stdout, stderr) => {
(err) => {
if (err) {
process.exit(1);
}
@@ -212,7 +206,7 @@ gulp.task('test:api-v3:integration:separate-server', (done) => {
let runner = exec(
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
{maxBuffer: 500 * 1024},
(err, stdout, stderr) => done(err)
(err) => done(err)
);
pipe(runner);

View File

@@ -1,6 +1,5 @@
import fs from 'fs';
import _ from 'lodash';
import nconf from 'nconf';
import gulp from 'gulp';
import { postToSlack, conf } from './taskHelper';
@@ -12,8 +11,82 @@ const SLACK_CONFIG = {
const LOCALES = './website/common/locales/';
const ENGLISH_LOCALE = `${LOCALES}en/`;
function getArrayOfLanguages () {
let languages = fs.readdirSync(LOCALES);
languages.shift(); // Remove README.md from array of languages
return languages;
}
const ALL_LANGUAGES = getArrayOfLanguages();
function stripOutNonJsonFiles (collection) {
let onlyJson = _.filter(collection, (file) => {
return file.match(/[a-zA-Z]*\.json/);
});
return onlyJson;
}
function eachTranslationFile (languages, cb) {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
_.each(languages, (lang) => {
_.each(jsonFiles, (filename) => {
let parsedTranslationFile;
try {
const translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`);
parsedTranslationFile = JSON.parse(translationFile);
} catch (err) {
return cb(err);
}
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
let parsedEnglishFile = JSON.parse(englishFile);
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
});
});
}
function eachTranslationString (languages, cb) {
eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => {
if (error) return;
_.each(englishJSON, (string, key) => {
const translationString = translationJSON[key];
cb(language, filename, key, string, translationString);
});
});
}
function formatMessageForPosting (msg, items) {
let body = `*Warning:* ${msg}`;
body += '\n\n```\n';
body += items.join('\n');
body += '\n```';
return body;
}
function getStringsWith (json, interpolationRegex) {
let strings = {};
_.each(json, (fileName) => {
const rawFile = fs.readFileSync(ENGLISH_LOCALE + fileName);
const parsedJson = JSON.parse(rawFile);
strings[fileName] = {};
_.each(parsedJson, (value, key) => {
const match = value.match(interpolationRegex);
if (match) strings[fileName][key] = match;
});
});
return strings;
}
const malformedStringExceptions = {
messageDropFood: true,
armoireFood: true,
@@ -23,7 +96,6 @@ const malformedStringExceptions = {
gulp.task('transifex', ['transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings']);
gulp.task('transifex:missingFiles', () => {
let missingStrings = [];
eachTranslationFile(ALL_LANGUAGES, (error) => {
@@ -40,7 +112,6 @@ gulp.task('transifex:missingFiles', () => {
});
gulp.task('transifex:missingStrings', () => {
let missingStrings = [];
eachTranslationString(ALL_LANGUAGES, (language, filename, key, englishString, translationString) => {
@@ -58,7 +129,6 @@ gulp.task('transifex:missingStrings', () => {
});
gulp.task('transifex:malformedStrings', () => {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
let interpolationRegex = /<%= [a-zA-Z]* %>/g;
let stringsToLookFor = getStringsWith(jsonFiles, interpolationRegex);
@@ -66,25 +136,23 @@ gulp.task('transifex:malformedStrings', () => {
let stringsWithMalformedInterpolations = [];
let stringsWithIncorrectNumberOfInterpolations = [];
let count = 0;
_.each(ALL_LANGUAGES, function (lang) {
_.each(stringsToLookFor, function (strings, file) {
let translationFile = fs.readFileSync(LOCALES + lang + '/' + file);
_.each(ALL_LANGUAGES, (lang) => {
_.each(stringsToLookFor, (strings, filename) => {
let translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`);
let parsedTranslationFile = JSON.parse(translationFile);
_.each(strings, function (value, key) {
_.each(strings, (value, key) => { // eslint-disable-line max-nested-callbacks
let translationString = parsedTranslationFile[key];
if (!translationString) return;
let englishOccurences = stringsToLookFor[file][key];
let englishOccurences = stringsToLookFor[filename][key];
let translationOccurences = translationString.match(interpolationRegex);
if (!translationOccurences) {
let malformedString = `${lang} - ${file} - ${key} - ${translationString}`;
let malformedString = `${lang} - ${filename} - ${key} - ${translationString}`;
stringsWithMalformedInterpolations.push(malformedString);
} else if (englishOccurences.length !== translationOccurences.length && !malformedStringExceptions[key]) {
let missingInterpolationString = `${lang} - ${file} - ${key} - ${translationString}`;
let missingInterpolationString = `${lang} - ${filename} - ${key} - ${translationString}`;
stringsWithIncorrectNumberOfInterpolations.push(missingInterpolationString);
}
});
@@ -103,74 +171,3 @@ gulp.task('transifex:malformedStrings', () => {
postToSlack(formattedMessage, SLACK_CONFIG);
}
});
function getArrayOfLanguages () {
let languages = fs.readdirSync(LOCALES);
languages.shift(); // Remove README.md from array of languages
return languages;
}
function eachTranslationFile (languages, cb) {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
_.each(languages, (lang) => {
_.each(jsonFiles, (filename) => {
try {
var translationFile = fs.readFileSync(LOCALES + lang + '/' + filename);
var parsedTranslationFile = JSON.parse(translationFile);
} catch (err) {
return cb(err);
}
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
let parsedEnglishFile = JSON.parse(englishFile);
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
});
});
}
function eachTranslationString (languages, cb) {
eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => {
if (error) return;
_.each(englishJSON, (string, key) => {
var translationString = translationJSON[key];
cb(language, filename, key, string, translationString);
});
});
}
function formatMessageForPosting (msg, items) {
let body = `*Warning:* ${msg}`;
body += '\n\n```\n';
body += items.join('\n');
body += '\n```';
return body;
}
function getStringsWith (json, interpolationRegex) {
var strings = {};
_.each(json, function (file_name) {
var raw_file = fs.readFileSync(ENGLISH_LOCALE + file_name);
var parsed_json = JSON.parse(raw_file);
strings[file_name] = {};
_.each(parsed_json, function (value, key) {
var match = value.match(interpolationRegex);
if (match) strings[file_name][key] = match;
});
});
return strings;
}
function stripOutNonJsonFiles (collection) {
let onlyJson = _.filter(collection, (file) => {
return file.match(/[a-zA-Z]*\.json/);
});
return onlyJson;
}

View File

@@ -12,7 +12,7 @@ import { resolve } from 'path';
* Get access to configruable values
*/
nconf.argv().env().file({ file: 'config.json' });
export var conf = nconf;
export const conf = nconf;
/*
* Kill a child process and any sub-children that process may have spawned.
@@ -26,11 +26,12 @@ export function kill (proc) {
pids.forEach(kill); return;
}
try {
exec(/^win/.test(process.platform)
? `taskkill /PID ${pid} /T /F`
: `kill -9 ${pid}`);
exec(/^win/.test(process.platform) ?
`taskkill /PID ${pid} /T /F` :
`kill -9 ${pid}`);
} catch (e) {
console.log(e); // eslint-disable-line no-console
}
catch (e) { console.log(e); }
});
};
@@ -44,21 +45,25 @@ export function kill (proc) {
* before failing.
*/
export function awaitPort (port, max = 60) {
return new Bluebird((reject, resolve) => {
let socket, timeout, interval;
return new Bluebird((rej, res) => {
let socket;
let timeout;
let interval;
timeout = setTimeout(() => {
clearInterval(interval);
reject(`Timed out after ${max} seconds`);
rej(`Timed out after ${max} seconds`);
}, max * 1000);
interval = setInterval(() => {
socket = net.connect({port: port}, () => {
socket = net.connect({port}, () => {
clearInterval(interval);
clearTimeout(timeout);
socket.destroy();
resolve();
}).on('error', () => { socket.destroy; });
res();
}).on('error', () => {
socket.destroy();
});
}, 1000);
});
}
@@ -67,8 +72,12 @@ export function awaitPort (port, max = 60) {
* Pipe the child's stdin and stderr to the parent process.
*/
export function pipe (child) {
child.stdout.on('data', (data) => { process.stdout.write(data); });
child.stderr.on('data', (data) => { process.stderr.write(data); });
child.stdout.on('data', (data) => {
process.stdout.write(data);
});
child.stderr.on('data', (data) => {
process.stderr.write(data);
});
}
/*
@@ -78,8 +87,8 @@ export function postToSlack (msg, config = {}) {
let slackUrl = nconf.get('SLACK_URL');
if (!slackUrl) {
console.error('No slack post url specified. Your message was:');
console.log(msg);
console.error('No slack post url specified. Your message was:'); // eslint-disable-line no-console
console.log(msg); // eslint-disable-line no-console
return;
}
@@ -89,15 +98,15 @@ export function postToSlack (msg, config = {}) {
channel: `#${config.channel || '#general'}`,
username: config.username || 'gulp task',
text: msg,
icon_emoji: `:${config.emoji || 'gulp'}:`,
icon_emoji: `:${config.emoji || 'gulp'}:`, // eslint-disable-line camelcase
})
.end((err, res) => {
if (err) console.error('Unable to post to slack', err);
.end((err) => {
if (err) console.error('Unable to post to slack', err); // eslint-disable-line no-console
});
}
export function runMochaTests (files, server, cb) {
require('../test/helpers/globals.helper');
require('../test/helpers/globals.helper'); // eslint-disable-line global-require
let mocha = new Mocha({reporter: 'spec'});
let tests = glob(files);
@@ -108,7 +117,7 @@ export function runMochaTests (files, server, cb) {
});
mocha.run((numberOfFailures) => {
if (!process.env.RUN_INTEGRATION_TEST_FOREVER) {
if (!process.env.RUN_INTEGRATION_TEST_FOREVER) { // eslint-disable-line no-process-env
if (server) kill(server);
process.exit(numberOfFailures);
}

View File

@@ -8,11 +8,10 @@
require('babel-register');
if (process.env.NODE_ENV === 'production') {
require('./gulp/gulp-apidoc');
require('./gulp/gulp-build');
require('./gulp/gulp-bootstrap');
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
require('./gulp/gulp-apidoc'); // eslint-disable-line global-require
require('./gulp/gulp-build'); // eslint-disable-line global-require
} else {
require('glob').sync('./gulp/gulp-*').forEach(require);
require('gulp').task('default', ['test']);
require('glob').sync('./gulp/gulp-*').forEach(require); // eslint-disable-line global-require
require('gulp').task('default', ['test']); // eslint-disable-line global-require
}

View File

@@ -16,7 +16,7 @@ var migrationName = '20140831_increase_gems_for_previous_contributions';
* https://github.com/HabitRPG/habitrpg/issues/3933
* Increase Number of Gems for Contributors
* author: Alys (d904bd62-da08-416b-a816-ba797c9ee265)
*
*
* Increase everyone's gems per their contribution level.
* Originally they were given 2 gems per tier.
* Now they are given 3 gems per tier for tiers 1,2,3
@@ -70,7 +70,7 @@ dbUsers.findEach(query, fields, function(err, user) {
var extraGems = tier; // tiers 1,2,3
if (tier > 3) { extraGems = 3 + (tier - 3) * 2; }
if (tier == 8) { extraGems = 11; }
extraBalance = extraGems / 4;
var extraBalance = extraGems / 4;
set['balance'] = user.balance + extraBalance;
// Capture current state of user:

View File

@@ -39,7 +39,7 @@ function findUsers(gt){
console.log('User: ', countUsers, user._id);
var update = {
$set: {};
$set: {}
};
if(user.auth && user.auth.local) {
@@ -60,4 +60,4 @@ function findUsers(gt){
});
};
findUsers();
findUsers();

View File

@@ -0,0 +1,111 @@
var migrationName = '20171030_jackolanterns.js';
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* Award the Jack-O'-Lantern ladder:
* Ghost Jack-O-Lantern Mount to owners of Ghost Jack-O-Lantern Pet
* Ghost Jack-O-Lantern Pet to owners of Jack-O-Lantern Mount
* Jack-O-Lantern Mount to owners of Jack-O-Lantern Pet
* Jack-O-Lantern Pet to everyone else
*/
var monk = require('monk');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers(lastId) {
// specify a query to limit the affected users (empty for all users):
var query = {
'migration':{$ne:migrationName},
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
'items.pets',
'items.mounts',
] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch(function (err) {
console.log(err);
return exiting(1, 'ERROR! ' + err);
});
}
var progressCount = 1000;
var count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
var userPromises = users.map(updateUser);
var lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(function () {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
var set = {};
var 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,
};
if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Ghost']) {
set = {'migration':migrationName, 'items.mounts.JackOLantern-Ghost': true};
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Base']) {
set = {'migration':migrationName, 'items.pets.JackOLantern-Ghost': 5};
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Base']) {
set = {'migration':migrationName, 'items.mounts.JackOLantern-Base': true};
} else {
set = {'migration':migrationName, 'items.pets.JackOLantern-Base': 5};
}
dbUsers.update({_id: user._id}, {$set:set, $inc:inc});
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
}
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
module.exports = processUsers;

View File

@@ -0,0 +1,128 @@
var migrationName = '20171117_turkey_ladder.js';
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* Award the Turkey Day ladder:
* Grant Turkey Costume to those who have the Gilded Turkey mount
* Grant Gilded Turkey mount to those who have the Gilded Turkey pet
* Grant Gilded Turkey pet to those who have the Base Turkey mount
* Grant Base Turkey mount to those who have the Base Turkey pet
* Grant Base Turkey pet to those who have none of the above yet
*/
var monk = require('monk');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers(lastId) {
// specify a query to limit the affected users (empty for all users):
var query = {
'migration':{$ne:migrationName},
'auth.timestamps.loggedin':{$gt:new Date('2017-11-01')},
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
'items.pets',
'items.mounts',
] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch(function (err) {
console.log(err);
return exiting(1, 'ERROR! ' + err);
});
}
var progressCount = 1000;
var count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
var userPromises = users.map(updateUser);
var lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(function () {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
var set = {};
if (user && user.items && user.items.mounts && user.items.mounts['Turkey-Gilded']) {
set = {
migration: migrationName,
'items.gear.owned.head_special_turkeyHelmBase': false,
'items.gear.owned.armor_special_turkeyArmorBase': false,
'items.gear.owned.back_special_turkeyTailBase': false,
};
var push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_turkeyHelmBase',
_id: monk.id(),
},
{
type: 'marketGear',
path: 'gear.flat.armor_special_turkeyArmorBase',
_id: monk.id(),
},
{
type: 'marketGear',
path: 'gear.flat.back_special_turkeyTailBase',
_id: monk.id(),
},
];
} else if (user && user.items && user.items.pets && user.items.pets['Turkey-Gilded']) {
set = {'migration':migrationName, 'items.mounts.Turkey-Gilded':true};
} else if (user && user.items && user.items.mounts && user.items.mounts['Turkey-Base']) {
set = {'migration':migrationName, 'items.pets.Turkey-Gilded':5};
} else if (user && user.items && user.items.pets && user.items.pets['Turkey-Base']) {
set = {'migration':migrationName, 'items.mounts.Turkey-Base':true};
} else {
set = {'migration':migrationName, 'items.pets.Turkey-Base':5};
}
dbUsers.update({_id: user._id}, {$set: set});
if (push) {
dbUsers.update({_id: user._id}, {$push: {pinnedItems: {$each: push}}});
}
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
}
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
module.exports = processUsers;

View File

@@ -17,8 +17,12 @@ function setUpServer () {
setUpServer();
// Replace this with your migration
var processUsers = require('./groups/update-groups-with-group-plans');
const processUsers = require('./users/account-transfer');
processUsers()
.catch(function (err) {
console.log(err)
.then(() => {
process.exit();
})
.catch(function (err) {
console.log(err);
process.exit();
});

View File

@@ -2,7 +2,7 @@ var _id = '';
var update = {
$addToSet: {
'purchased.plan.mysteryItems':{
$each:['armor_mystery_201710','head_mystery_201710']
$each:['armor_mystery_201711','body_mystery_201711']
}
}
};

View File

@@ -65,19 +65,29 @@ function updateUser (user) {
set = {'migration':migrationName};
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.back_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.back_special_takeThis', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.body_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_takeThis', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.head_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_takeThis', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.armor_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_takeThis', '_id': monk.id()}};
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.weapon_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', '_id': monk.id()}};
} else {
set = {'migration':migrationName, 'items.gear.owned.shield_special_takeThis':false};
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.shield_special_takeThis', '_id': monk.id()}};
}
dbUsers.update({_id: user._id}, {$set:set});
if (push) {
dbUsers.update({_id: user._id}, {$set: set, $push: push});
} else {
dbUsers.update({_id: user._id}, {$set: set});
}
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');

View File

@@ -0,0 +1,38 @@
var migrationName = 'AccountTransfer';
var authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
var authorUuid = ''; //... own data is done
/*
* This migraition will copy user data from prod to test
*/
const monk = require('monk');
const connectionString = '';
const Users = monk(connectionString).get('users', { castIds: false });
import uniq from 'lodash/uniq';
import Bluebird from 'bluebird';
module.exports = async function accountTransfer () {
const fromAccountId = '';
const toAccountId = '';
const fromAccount = await Users.findOne({_id: fromAccountId});
const toAccount = await Users.findOne({_id: toAccountId});
const newMounts = Object.assign({}, fromAccount.items.mounts, toAccount.items.mounts);
const newPets = Object.assign({}, fromAccount.items.pets, toAccount.items.pets);
const newBackgrounds = Object.assign({}, fromAccount.purchased.background, toAccount.purchased.background);
await Users.update({_id: toAccountId}, {
$set: {
'items.pets': newPets,
'items.mounts': newMounts,
'purchased.background': newBackgrounds,
},
})
.then((result) => {
console.log(result);
});
};

2598
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.6.0",
"version": "4.13.2",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",
@@ -31,8 +31,8 @@
"bcrypt": "^1.0.2",
"bluebird": "^3.3.5",
"body-parser": "^1.15.0",
"bootstrap": "4.0.0-alpha.6",
"bootstrap-vue": "1.0.0-beta.7",
"bootstrap": "4.0.0-beta.2",
"bootstrap-vue": "^1.0.2",
"browserify": "~12.0.1",
"compression": "^1.6.1",
"connect-ratelimit": "0.0.7",
@@ -116,13 +116,14 @@
"validator": "^4.9.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"vue": "^2.1.0",
"vue-loader": "^11.0.0",
"vue": "^2.5.2",
"vue-loader": "^13.3.0",
"vue-mugen-scroll": "^0.2.1",
"vue-router": "^2.0.0-rc.5",
"vue-router": "^3.0.0",
"vue-style-loader": "^3.0.0",
"vue-template-compiler": "^2.1.10",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker#45e607a7bccf4e3e089761b3b7b33e3f2c5dc21f",
"vue-template-compiler": "^2.5.2",
"vuedraggable": "^2.15.0",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker#825a866b6a9c52dd8c588a3e8b900880875ce914",
"webpack": "^2.2.1",
"webpack-merge": "^4.0.0",
"winston": "^2.1.0",
@@ -136,7 +137,7 @@
},
"scripts": {
"lint": "eslint --ext .js,.vue .",
"test": "npm run lint && gulp test && npm run client:unit && gulp apidoc",
"test": "npm run lint && gulp test && gulp apidoc",
"test:build": "gulp test:prepare:build",
"test:api-v3": "gulp test:api-v3",
"test:api-v3:unit": "gulp test:api-v3:unit",
@@ -148,7 +149,7 @@
"test:nodemon": "gulp test:nodemon",
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
"sprites": "gulp sprites:compile",
"client:dev": "gulp bootstrap && node webpack/dev-server.js",
"client:dev": "node webpack/dev-server.js",
"client:build": "gulp build:client",
"client:unit": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js --single-run",
"client:unit:watch": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js",

View File

@@ -4,6 +4,7 @@ import {
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { TAVERN_ID } from '../../../../../website/common/script/constants';
describe('GET challenges/groups/:groupId', () => {
context('Public Guild', () => {
@@ -181,4 +182,123 @@ describe('GET challenges/groups/:groupId', () => {
expect(foundChallengeIndex).to.eql(1);
});
});
context('Party', () => {
let party, user, nonMember, challenge, challenge2;
before(async () => {
let { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestParty',
type: 'party',
},
});
party = group;
user = groupLeader;
nonMember = await generateUser();
challenge = await generateChallenge(user, group);
challenge2 = await generateChallenge(user, group);
});
it('should prevent non-member from seeing challenges', async () => {
await expect(nonMember.get(`/challenges/groups/${party._id}`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('groupNotFound'),
});
});
it('should return group challenges for member with populated leader', async () => {
let challenges = await user.get(`/challenges/groups/${party._id}`);
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
});
});
it('should return group challenges for member using ID "party"', async () => {
let challenges = await user.get('/challenges/groups/party');
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
});
});
});
context('Tavern', () => {
let tavern, user, challenge, challenge2;
before(async () => {
user = await generateUser();
await user.update({balance: 0.5});
tavern = await user.get(`/groups/${TAVERN_ID}`);
challenge = await generateChallenge(user, tavern, {prize: 1});
challenge2 = await generateChallenge(user, tavern, {prize: 1});
});
it('should return tavern challenges with populated leader', async () => {
let challenges = await user.get(`/challenges/groups/${TAVERN_ID}`);
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
});
});
it('should return tavern challenges using ID "habitrpg', async () => {
let challenges = await user.get('/challenges/groups/habitrpg');
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
});
});
});
});

View File

@@ -12,6 +12,7 @@ import {
import { v4 as generateUUID } from 'uuid';
import { getMatchesByWordArray, removePunctuationFromString } from '../../../../../website/server/libs/stringUtils';
import bannedWords from '../../../../../website/server/libs/bannedWords';
import guildsAllowingBannedWords from '../../../../../website/server/libs/guildsAllowingBannedWords';
import * as email from '../../../../../website/server/libs/email';
import { IncomingWebhook } from '@slack/client';
import nconf from 'nconf';
@@ -96,6 +97,24 @@ describe('POST /chat', () => {
});
});
it('returns an error when chat message contains a banned word in a public guild', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
name: 'public guild',
type: 'guild',
privacy: 'public',
},
members: 1,
});
await expect(members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: bannedWordErrorMessage,
});
});
it('errors when word is part of a phrase', async () => {
let wordInPhrase = `phrase ${testBannedWordMessage} end`;
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase}))
@@ -161,7 +180,7 @@ describe('POST /chat', () => {
expect(message.message.id).to.exist;
});
it('does not error when sending a chat message containing a banned word to a public guild', async () => {
it('does not error when sending a chat message containing a banned word to a public guild in which banned words are allowed', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
name: 'public guild',
@@ -171,6 +190,8 @@ describe('POST /chat', () => {
members: 1,
});
guildsAllowingBannedWords[group._id] = true;
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
expect(message.message.id).to.exist;

View File

@@ -3,6 +3,7 @@ import {
generateUser,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import config from '../../../../../config.json';
import { v4 as generateUUID } from 'uuid';
describe('POST /groups/:id/chat/:id/clearflags', () => {
@@ -74,7 +75,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
expect(messages[0].flagCount).to.eql(0);
});
it('can unflag a system message', async () => {
it('can\'t flag a system message', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
type: 'party',
@@ -95,13 +96,15 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
await member.post('/user/class/cast/mpheal');
let [skillMsg] = await member.get(`/groups/${group.id}/chat`);
await member.post(`/groups/${group._id}/chat/${skillMsg.id}/flag`);
await admin.post(`/groups/${group._id}/chat/${skillMsg.id}/clearflags`);
let messages = await members[0].get(`/groups/${group._id}/chat`);
expect(messages[0].id).to.eql(skillMsg.id);
expect(messages[0].flagCount).to.eql(0);
await expect(member.post(`/groups/${group._id}/chat/${skillMsg.id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('messageCannotFlagSystemMessages', {communityManagerEmail: config.EMAILS.COMMUNITY_MANAGER_EMAIL}),
});
// let messages = await members[0].get(`/groups/${group._id}/chat`);
// expect(messages[0].id).to.eql(skillMsg.id);
// expect(messages[0].flagCount).to.eql(0);
});
});

View File

@@ -1,8 +1,8 @@
import {
generateUser,
translate as t,
resetHabiticaDB,
} from '../../../../helpers/api-v3-integration.helper';
import apiMessages from '../../../../../website/server/libs/apiMessages';
describe('GET /coupons/', () => {
let user;
@@ -19,7 +19,7 @@ describe('GET /coupons/', () => {
await expect(user.get('/coupons')).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('noSudoAccess'),
message: apiMessages('noSudoAccess'),
});
});

View File

@@ -4,6 +4,7 @@ import {
resetHabiticaDB,
} from '../../../../helpers/api-v3-integration.helper';
import couponCode from 'coupon-code';
import apiMessages from '../../../../../website/server/libs/apiMessages';
describe('POST /coupons/generate/:event', () => {
let user;
@@ -25,7 +26,7 @@ describe('POST /coupons/generate/:event', () => {
await expect(user.post('/coupons/generate/aaa')).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('noSudoAccess'),
message: apiMessages('noSudoAccess'),
});
});

View File

@@ -10,6 +10,8 @@ import { v4 as generateUUID } from 'uuid';
import {
each,
} from 'lodash';
import { model as User } from '../../../../../website/server/models/user';
import * as payments from '../../../../../website/server/libs/payments';
describe('POST /groups/:groupId/leave', () => {
let typesOfGroups = {
@@ -264,4 +266,45 @@ describe('POST /groups/:groupId/leave', () => {
expect(userWithNonExistentParty.party).to.eql({});
});
});
context('Leaving a group plan', () => {
it('cancels the free subscription', async () => {
// Create group
let { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: {
name: 'Test Private Guild',
type: 'guild',
},
members: 1,
});
let leader = groupLeader;
let member = members[0];
let userWithFreePlan = await User.findById(leader._id).exec();
// Create subscription
let paymentData = {
user: userWithFreePlan,
groupId: group._id,
sub: {
key: 'basic_3mo',
},
customerId: 'customer-id',
paymentMethod: 'Payment Method',
headers: {
'x-client': 'habitica-web',
'user-agent': '',
},
};
await payments.createSubscription(paymentData);
await member.sync();
expect(member.purchased.plan.planId).to.equal('group_plan_auto');
expect(member.purchased.plan.dateTerminated).to.not.exist;
// Leave
await member.post(`/groups/${group._id}/leave`);
await member.sync();
expect(member.purchased.plan.dateTerminated).to.exist;
});
});
});

View File

@@ -13,14 +13,44 @@ describe('POST /notifications/:notificationId/read', () => {
it('errors when notification is not found', async () => {
let dummyId = generateUUID();
await expect(user.post(`/notifications/${dummyId}/read`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageNotificationNotFound'),
});
await expect(user.post(`/notifications/${dummyId}/read`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageNotificationNotFound'),
});
});
xit('removes a notification', async () => {
it('removes a notification', async () => {
expect(user.notifications.length).to.equal(0);
const id = generateUUID();
const id2 = generateUUID();
await user.update({
notifications: [{
id,
type: 'DROPS_ENABLED',
data: {},
}, {
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
}],
});
await user.sync();
expect(user.notifications.length).to.equal(2);
const res = await user.post(`/notifications/${id}/read`);
expect(res).to.deep.equal([{
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
}]);
await user.sync();
expect(user.notifications.length).to.equal(1);
expect(user.notifications[0].id).to.equal(id2);
});
});

View File

@@ -0,0 +1,66 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('POST /notifications/:notificationId/read', () => {
let user;
before(async () => {
user = await generateUser();
});
it('errors when notification is not found', async () => {
let dummyId = generateUUID();
await expect(user.post('/notifications/read', {
notificationIds: [dummyId],
})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageNotificationNotFound'),
});
});
it('removes multiple notifications', async () => {
expect(user.notifications.length).to.equal(0);
const id = generateUUID();
const id2 = generateUUID();
const id3 = generateUUID();
await user.update({
notifications: [{
id,
type: 'DROPS_ENABLED',
data: {},
}, {
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
}, {
id: id3,
type: 'CRON',
data: {},
}],
});
await user.sync();
expect(user.notifications.length).to.equal(3);
const res = await user.post('/notifications/read', {
notificationIds: [id, id3],
});
expect(res).to.deep.equal([{
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
}]);
await user.sync();
expect(user.notifications.length).to.equal(1);
expect(user.notifications[0].id).to.equal(id2);
});
});

View File

@@ -141,6 +141,16 @@ describe('DELETE /tasks/:id', () => {
});
});
it('removes a task from user.tasksOrder'); // TODO
it('removes a task from user.tasksOrder', async () => {
let task = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
});
await user.del(`/tasks/${task._id}`);
await user.sync();
expect(user.tasksOrder.habits.indexOf(task._id)).to.eql(-1);
});
});
});

View File

@@ -130,6 +130,7 @@ describe('POST /tasks/:id/score/:direction', () => {
});
it('uncompletes todo when direction is down', async () => {
await user.post(`/tasks/${todo._id}/score/up`);
await user.post(`/tasks/${todo._id}/score/down`);
let updatedTask = await user.get(`/tasks/${todo._id}`);
@@ -137,9 +138,23 @@ describe('POST /tasks/:id/score/:direction', () => {
expect(updatedTask.dateCompleted).to.be.a('undefined');
});
it('scores up todo even if it is already completed'); // Yes?
it('doesn\'t let a todo be completed twice', async () => {
await user.post(`/tasks/${todo._id}/score/up`);
await expect(user.post(`/tasks/${todo._id}/score/up`))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('sessionOutdated'),
});
});
it('scores down todo even if it is already uncompleted'); // Yes?
it('doesn\'t let a todo be uncompleted twice', async () => {
await expect(user.post(`/tasks/${todo._id}/score/down`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('sessionOutdated'),
});
});
context('user stats when direction is up', () => {
let updatedUser;
@@ -163,23 +178,25 @@ describe('POST /tasks/:id/score/:direction', () => {
});
context('user stats when direction is down', () => {
let updatedUser;
let updatedUser, initialUser;
beforeEach(async () => {
await user.post(`/tasks/${todo._id}/score/up`);
initialUser = await user.get('/user');
await user.post(`/tasks/${todo._id}/score/down`);
updatedUser = await user.get('/user');
});
it('decreases user\'s mp', () => {
expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp);
expect(updatedUser.stats.mp).to.be.lessThan(initialUser.stats.mp);
});
it('decreases user\'s exp', () => {
expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp);
expect(updatedUser.stats.exp).to.be.lessThan(initialUser.stats.exp);
});
it('decreases user\'s gold', () => {
expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp);
expect(updatedUser.stats.gp).to.be.lessThan(initialUser.stats.gp);
});
});
});
@@ -202,6 +219,7 @@ describe('POST /tasks/:id/score/:direction', () => {
});
it('uncompletes daily when direction is down', async () => {
await user.post(`/tasks/${daily._id}/score/up`);
await user.post(`/tasks/${daily._id}/score/down`);
let task = await user.get(`/tasks/${daily._id}`);
@@ -222,9 +240,22 @@ describe('POST /tasks/:id/score/:direction', () => {
expect(task.nextDue.length).to.eql(6);
});
it('scores up daily even if it is already completed'); // Yes?
it('doesn\'t let a daily be completed twice', async () => {
await user.post(`/tasks/${daily._id}/score/up`);
await expect(user.post(`/tasks/${daily._id}/score/up`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('sessionOutdated'),
});
});
it('scores down daily even if it is already uncompleted'); // Yes?
it('doesn\'t let a daily be uncompleted twice', async () => {
await expect(user.post(`/tasks/${daily._id}/score/down`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('sessionOutdated'),
});
});
context('user stats when direction is up', () => {
let updatedUser;
@@ -248,23 +279,25 @@ describe('POST /tasks/:id/score/:direction', () => {
});
context('user stats when direction is down', () => {
let updatedUser;
let updatedUser, initialUser;
beforeEach(async () => {
await user.post(`/tasks/${daily._id}/score/up`);
initialUser = await user.get('/user');
await user.post(`/tasks/${daily._id}/score/down`);
updatedUser = await user.get('/user');
});
it('decreases user\'s mp', () => {
expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp);
expect(updatedUser.stats.mp).to.be.lessThan(initialUser.stats.mp);
});
it('decreases user\'s exp', () => {
expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp);
expect(updatedUser.stats.exp).to.be.lessThan(initialUser.stats.exp);
});
it('decreases user\'s gold', () => {
expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp);
expect(updatedUser.stats.gp).to.be.lessThan(initialUser.stats.gp);
});
});
});

View File

@@ -40,9 +40,13 @@ describe('POST /tasks/:taskId/move/to/:position', () => {
let taskToMove = tasks[1];
expect(taskToMove.text).to.equal('habit 2');
let newOrder = await user.post(`/tasks/${tasks[1]._id}/move/to/3`);
await user.sync();
expect(newOrder[3]).to.equal(taskToMove._id);
expect(newOrder.length).to.equal(5);
expect(user.tasksOrder.habits).to.eql(newOrder);
});
it('can move task to new position using alias', async () => {

View File

@@ -82,6 +82,13 @@ describe('POST /tasks/:id/score/:direction', () => {
});
it('should update the history', async () => {
let newCron = new Date(2015, 11, 20);
await user.post('/debug/set-cron', {
lastCron: newCron,
});
await user.post('/cron');
await user.post(`/tasks/${usersChallengeTaskId}/score/up`);
let tasks = await user.get(`/tasks/challenge/${challenge._id}`);

View File

@@ -75,15 +75,6 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => {
});
});
it('returns error when non leader tries to create a task', async () => {
await expect(member.post(`/tasks/${task._id}/unassign/${member._id}`))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('onlyGroupLeaderCanEditTasks'),
});
});
it('unassigns a user from a task', async () => {
await user.post(`/tasks/${task._id}/unassign/${member._id}`);
@@ -129,4 +120,26 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => {
expect(groupTask[0].group.assignedUsers).to.not.contain(member._id);
expect(syncedTask).to.not.exist;
});
it('allows a user to unassign themselves', async () => {
await member.post(`/tasks/${task._id}/unassign/${member._id}`);
let groupTask = await user.get(`/tasks/group/${guild._id}`);
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
expect(groupTask[0].group.assignedUsers).to.not.contain(member._id);
expect(syncedTask).to.not.exist;
});
// @TODO: Which do we want? The user to unassign themselves or not. This test was in
// here, but then we had a request to allow to unaissgn.
xit('returns error when non leader tries to unassign their a task', async () => {
await expect(member.post(`/tasks/${task._id}/unassign/${member._id}`))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('onlyGroupLeaderCanEditTasks'),
});
});
});

View File

@@ -308,7 +308,7 @@ describe('DELETE /user', () => {
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('incorrectDeletePhrase'),
message: t('incorrectDeletePhrase', {magicWord: 'DELETE'}),
});
});

View File

@@ -1,7 +1,7 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
} from '../../../../../helpers/api-integration/v3';
describe('POST /user/allocate', () => {
let user;

View File

@@ -0,0 +1,40 @@
import {
generateUser,
translate as t,
} from '../../../../../helpers/api-integration/v3';
describe('POST /user/allocate-bulk', () => {
let user;
const statsUpdate = {
stats: {
con: 1,
str: 2,
},
};
beforeEach(async () => {
user = await generateUser();
});
// More tests in common code unit tests
it('returns an error if user does not have enough points', async () => {
await expect(user.post('/user/allocate-bulk', statsUpdate))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('notEnoughAttrPoints'),
});
});
it('allocates attribute points', async () => {
await user.update({'stats.points': 3});
await user.post('/user/allocate-bulk', statsUpdate);
await user.sync();
expect(user.stats.con).to.equal(1);
expect(user.stats.str).to.equal(2);
expect(user.stats.points).to.equal(0);
});
});

View File

@@ -1,6 +1,6 @@
import {
generateUser,
} from '../../../../helpers/api-integration/v3';
} from '../../../../../helpers/api-integration/v3';
describe('POST /user/allocate-now', () => {
// More tests in common code unit tests

View File

@@ -365,6 +365,72 @@ describe('cron', () => {
expect(user.history.todos).to.be.lengthOf(1);
});
it('should remove completed todos from users taskOrder list', () => {
tasksByType.todos = [];
user.tasksOrder.todos = [];
let todo = {
text: 'test todo',
type: 'todo',
value: 0,
};
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
tasksByType.todos.push(task);
task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
tasksByType.todos.push(task);
tasksByType.todos[0].completed = true;
user.tasksOrder.todos = tasksByType.todos.map(taskTodo => {
return taskTodo._id;
});
// Since ideally tasksByType should not contain completed todos, fake ids should be filtered too
user.tasksOrder.todos.push('00000000-0000-0000-0000-000000000000');
expect(tasksByType.todos).to.be.lengthOf(2);
expect(user.tasksOrder.todos).to.be.lengthOf(3);
cron({user, tasksByType, daysMissed, analytics});
// user.tasksOrder.todos should be filtered while tasks by type remains unchanged
expect(tasksByType.todos).to.be.lengthOf(2);
expect(user.tasksOrder.todos).to.be.lengthOf(1);
});
it('should preserve todos order in task list', () => {
tasksByType.todos = [];
user.tasksOrder.todos = [];
let todo = {
text: 'test todo',
type: 'todo',
value: 0,
};
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
tasksByType.todos.push(task);
task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
tasksByType.todos.push(task);
task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
tasksByType.todos.push(task);
// Set up user.tasksOrder list in a specific order
user.tasksOrder.todos = tasksByType.todos.map(todoTask => {
return todoTask._id;
}).reverse();
let original = user.tasksOrder.todos; // Preserve the original order
cron({user, tasksByType, daysMissed, analytics});
let listsAreEqual = true;
user.tasksOrder.todos.forEach((taskId, index) => {
if (original[index]._id !== taskId) {
listsAreEqual = false;
}
});
expect(listsAreEqual);
expect(user.tasksOrder.todos).to.be.lengthOf(original.length);
});
});
describe('dailys', () => {

View File

@@ -475,7 +475,7 @@ describe('Purchasing a group plan for group', () => {
let updatedUser = await User.findById(recipient._id).exec();
expect(updatedUser.purchased.plan.extraMonths).to.within(3, 4);
expect(updatedUser.purchased.plan.extraMonths).to.within(3, 5);
});
it('adds months to members with existing recurring subscription (Paypal)', async () => {

View File

@@ -13,6 +13,9 @@ import analyticsService from '../../../../../website/server/libs/analyticsServic
import * as cronLib from '../../../../../website/server/libs/cron';
import { v4 as generateUUID } from 'uuid';
const CRON_TIMEOUT_WAIT = new Date(60 * 60 * 1000).getTime();
const CRON_TIMEOUT_UNIT = new Date(60 * 1000).getTime();
describe('cron middleware', () => {
let res, req;
let user;
@@ -235,7 +238,13 @@ describe('cron middleware', () => {
sandbox.spy(cronLib, 'recoverCron');
sandbox.stub(User, 'update')
.withArgs({ _id: user._id, _cronSignature: 'NOT_RUNNING' })
.withArgs({
_id: user._id,
$or: [
{_cronSignature: 'NOT_RUNNING'},
{_cronSignature: {$lt: sinon.match.number}},
],
})
.returns({
exec () {
return Promise.resolve(updatedUser);
@@ -251,4 +260,48 @@ describe('cron middleware', () => {
});
});
});
it('cronSignature less than an hour ago should error', async () => {
user.lastCron = moment(new Date()).subtract({days: 2});
let now = new Date();
await User.update({
_id: user._id,
}, {
$set: {
_cronSignature: now.getTime() - CRON_TIMEOUT_WAIT + CRON_TIMEOUT_UNIT,
},
}).exec();
await user.save();
let expectedErrMessage = `Impossible to recover from cron for user ${user._id}.`;
await new Promise((resolve, reject) => {
cronMiddleware(req, res, (err) => {
if (!err) return reject(new Error('Cron should have failed.'));
expect(err.message).to.be.equal(expectedErrMessage);
resolve();
});
});
});
it('cronSignature longer than an hour ago should allow cron', async () => {
user.lastCron = moment(new Date()).subtract({days: 2});
let now = new Date();
await User.update({
_id: user._id,
}, {
$set: {
_cronSignature: now.getTime() - CRON_TIMEOUT_WAIT - CRON_TIMEOUT_UNIT,
},
}).exec();
await user.save();
await new Promise((resolve, reject) => {
cronMiddleware(req, res, (err) => {
if (err) return reject(err);
expect(moment(now).isSame(user.auth.timestamps.loggedin, 'day'));
expect(user._cronSignature).to.be.equal('NOT_RUNNING');
resolve();
});
});
});
});

View File

@@ -7,6 +7,7 @@ import {
import i18n from '../../../../../website/common/script/i18n';
import { ensureAdmin, ensureSudo } from '../../../../../website/server/middlewares/ensureAccessRight';
import { NotAuthorized } from '../../../../../website/server/libs/errors';
import apiMessages from '../../../../../website/server/libs/apiMessages';
describe('ensure access middlewares', () => {
let res, req, next;
@@ -42,7 +43,7 @@ describe('ensure access middlewares', () => {
ensureSudo(req, res, next);
expect(next).to.be.calledWith(new NotAuthorized(i18n.t('noSudoAccess')));
expect(next).to.be.calledWith(new NotAuthorized(apiMessages('noSudoAccess')));
});
it('passes when user is a sudo user', () => {

View File

@@ -0,0 +1,31 @@
import Vue from 'vue';
import MembersModalComponent from 'client/components/groups/membersModal.vue';
describe('Members Modal Component', () => {
describe('Party Sort', () => {
let CTor;
let vm;
beforeEach(() => {
CTor = Vue.extend(MembersModalComponent);
vm = new CTor().$mount();
});
afterEach(() => {
vm.$destroy();
});
it('should have an empty object as sort-option at start', () => {
const defaultData = vm.data();
expect(defaultData.sortOption).to.eq({});
});
it('should accept sort-option object', () => {
const sortOption = vm.data().sortOption[0];
vm.sort(sortOption);
Vue.nextTick(() => {
expect(vm.data().sortOption).to.eq(sortOption);
});
});
});
});

View File

@@ -127,14 +127,6 @@ describe('shared.ops.addTask', () => {
expect(addTask(user)._edit).to.not.be.ok;
});
it('respects tagsCollapsed preference', () => {
user.preferences.tagsCollapsed = true;
expect(addTask(user)._tags).to.not.be.ok;
user.preferences.tagsCollapsed = false;
expect(addTask(user)._tags).to.be.ok;
});
it('respects advancedCollapsed preference', () => {
user.preferences.advancedCollapsed = true;
expect(addTask(user)._advanced).not.be.ok;

View File

@@ -231,13 +231,30 @@ describe('shared.ops.purchase', () => {
context('bulk purchase', () => {
let userGemAmount = 10;
before(() => {
beforeEach(() => {
user.balance = userGemAmount;
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = 0;
user.purchased.plan.customerId = 'customer-id';
});
it('errors when user does not have enough gems', (done) => {
user.balance = 1;
let type = 'eggs';
let key = 'TigerCub';
try {
purchase(user, {
params: {type, key},
quantity: 2,
});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('notEnoughGems'));
done();
}
});
it('makes bulk purchases of gems', () => {
let [, message] = purchase(user, {
params: {type: 'gems', key: 'gem'},

View File

@@ -1,12 +1,12 @@
import allocate from '../../../website/common/script/ops/allocate';
import allocate from '../../../../website/common/script/ops/stats/allocate';
import {
BadRequest,
NotAuthorized,
} from '../../../website/common/script/libs/errors';
import i18n from '../../../website/common/script/i18n';
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import {
generateUser,
} from '../../helpers/common.helper';
} from '../../../helpers/common.helper';
describe('shared.ops.allocate', () => {
let user;

View File

@@ -0,0 +1,98 @@
import allocateBulk from '../../../../website/common/script/ops/stats/allocateBulk';
import {
BadRequest,
NotAuthorized,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import {
generateUser,
} from '../../../helpers/common.helper';
describe('shared.ops.allocateBulk', () => {
let user;
beforeEach(() => {
user = generateUser();
});
it('throws an error if an invalid attribute is supplied', (done) => {
try {
allocateBulk(user, {
body: {
stats: {
invalid: 1,
str: 2,
},
},
});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidAttribute', {attr: 'invalid'}));
done();
}
});
it('throws an error if the stats are not supplied', (done) => {
try {
allocateBulk(user);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('statsObjectRequired'));
done();
}
});
it('throws an error if the user doesn\'t have attribute points', (done) => {
try {
allocateBulk(user, {
body: {
stats: {
int: 1,
str: 2,
},
},
});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('notEnoughAttrPoints'));
done();
}
});
it('throws an error if the user doesn\'t have enough attribute points', (done) => {
user.stats.points = 1;
try {
allocateBulk(user, {
body: {
stats: {
int: 1,
str: 2,
},
},
});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('notEnoughAttrPoints'));
done();
}
});
it('allocates attribute points', () => {
user.stats.points = 3;
expect(user.stats.int).to.equal(0);
expect(user.stats.str).to.equal(0);
allocateBulk(user, {
body: {
stats: {
int: 1,
str: 2,
},
},
});
expect(user.stats.str).to.equal(2);
expect(user.stats.int).to.equal(1);
expect(user.stats.points).to.equal(0);
});
});

View File

@@ -1,7 +1,7 @@
import allocateNow from '../../../website/common/script/ops/allocateNow';
import allocateNow from '../../../../website/common/script/ops/stats/allocateNow';
import {
generateUser,
} from '../../helpers/common.helper';
} from '../../../helpers/common.helper';
describe('shared.ops.allocateNow', () => {
let user;

View File

@@ -644,7 +644,27 @@ describe('shouldDo', () => {
day = moment();
dailyTask.repeat[DAY_MAPPING[day.day()]] = true;
dailyTask.everyX = 3;
let threeWeeksFromToday = day.add(6, 'weeks').day(day.day()).toDate();
const threeWeeksFromToday = day.add(6, 'weeks').day(day.day()).toDate();
expect(shouldDo(threeWeeksFromToday, dailyTask, options)).to.equal(true);
});
it('activates Daily on every (x) week on weekday across a year', () => {
dailyTask.repeat = {
su: false,
s: false,
f: false,
th: false,
w: false,
t: false,
m: false,
};
day = moment('2017-11-19');
dailyTask.startDate = day.toDate();
dailyTask.repeat[DAY_MAPPING[day.day()]] = true;
dailyTask.everyX = 3;
const threeWeeksFromToday = moment('2018-01-21');
expect(shouldDo(threeWeeksFromToday, dailyTask, options)).to.equal(true);
});
@@ -970,7 +990,7 @@ describe('shouldDo', () => {
m: false,
};
let today = moment('2017-01-26');
let today = moment('2017-01-26:00:00.000-00:00');
let week = today.monthWeek();
let dayOfWeek = today.day();
dailyTask.startDate = today.toDate();
@@ -979,7 +999,7 @@ describe('shouldDo', () => {
dailyTask.everyX = 2;
dailyTask.frequency = 'monthly';
day = moment('2017-03-24');
day = moment('2017-03-24:00:00.000-00:00');
expect(shouldDo(day, dailyTask, options)).to.equal(false);
});
@@ -995,7 +1015,7 @@ describe('shouldDo', () => {
m: false,
};
let today = moment('2017-01-27');
let today = moment('2017-01-27:00:00.000-00:00');
let week = today.monthWeek();
let dayOfWeek = today.day();
dailyTask.startDate = today.toDate();
@@ -1004,7 +1024,7 @@ describe('shouldDo', () => {
dailyTask.everyX = 2;
dailyTask.frequency = 'monthly';
day = moment('2017-03-24');
day = moment('2017-03-24:00:00.000-00:00');
expect(shouldDo(day, dailyTask, options)).to.equal(true);
});
@@ -1020,7 +1040,7 @@ describe('shouldDo', () => {
m: false,
};
let today = moment('2017-01-27');
let today = moment('2017-01-27:00:00.000-00:00');
let week = today.monthWeek();
let dayOfWeek = today.day();
dailyTask.startDate = today.toDate();
@@ -1029,7 +1049,7 @@ describe('shouldDo', () => {
dailyTask.everyX = 2;
dailyTask.frequency = 'monthly';
day = moment('2017-03-24');
day = moment('2017-03-24:00:00.000-00:00');
expect(shouldDo(day, dailyTask, options)).to.equal(true);
});

0
vagrant_scripts/install_node.sh Normal file → Executable file
View File

View File

@@ -3,6 +3,14 @@ const path = require('path');
const staticAssetsDirectory = './website/static/.'; // The folder where static files (not processed) live
const prodEnv = require('./prod.env');
const devEnv = require('./dev.env');
const nconf = require('nconf');
const setupNconf = require('../../website/server/libs/setupNconf');
let configFile = path.join(path.resolve(__dirname, '../../config.json'));
setupNconf(configFile);
const DEV_BASE_URL = nconf.get('BASE_URL');
module.exports = {
build: {
@@ -33,25 +41,25 @@ module.exports = {
assetsPublicPath: '/',
staticAssetsDirectory,
proxyTable: {
// proxy all requests starting with /api/v3 to localhost:3000
// proxy all requests starting with /api/v3 to IP:PORT as specified in the top-level config
'/api/v3': {
target: 'http://localhost:3000',
target: DEV_BASE_URL,
changeOrigin: true,
},
'/stripe': {
target: 'http://localhost:3000',
target: DEV_BASE_URL,
changeOrigin: true,
},
'/amazon': {
target: 'http://localhost:3000',
target: DEV_BASE_URL,
changeOrigin: true,
},
'/paypal': {
target: 'http://localhost:3000',
target: DEV_BASE_URL,
changeOrigin: true,
},
'/logout': {
target: 'http://localhost:3000',
target: DEV_BASE_URL,
changeOrigin: true,
},
},

View File

@@ -1,17 +1,11 @@
const nconf = require('nconf');
const { join, resolve } = require('path');
const setupNconf = require('../../website/server/libs/setupNconf');
const PATH_TO_CONFIG = join(resolve(__dirname, '../../config.json'));
let configFile = PATH_TO_CONFIG;
nconf
.argv()
.env()
.file('user', configFile);
nconf.set('IS_PROD', nconf.get('NODE_ENV') === 'production');
nconf.set('IS_DEV', nconf.get('NODE_ENV') === 'development');
nconf.set('IS_TEST', nconf.get('NODE_ENV') === 'test');
setupNconf(configFile);
// @TODO: Check if we can import from client. Items like admin emails can be imported
// and that should be prefered

View File

@@ -83,7 +83,7 @@ const baseConfig = {
loader: 'url-loader',
query: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]'),
name: utils.assetsPath('images/[name].[hash:7].[ext]'),
},
},
{
@@ -109,6 +109,7 @@ const baseConfig = {
loader: 'svg-url-loader',
options: {
limit: 10000,
name: utils.assetsPath('svg/[name].[hash:7].[ext]'),
},
},
{ loader: 'svgo-loader' },

View File

@@ -1,5 +1,6 @@
<template lang="pug">
#app(:class='{"casting-spell": castingSpell}')
amazon-payments-modal
snackbars
router-view(v-if="!isUserLoggedIn || isStaticPage")
template(v-else)
@@ -71,8 +72,8 @@
import axios from 'axios';
import { loadProgressBar } from 'axios-progress-bar';
import AppMenu from './components/appMenu';
import AppHeader from './components/appHeader';
import AppMenu from './components/header/menu';
import AppHeader from './components/header/index';
import AppFooter from './components/appFooter';
import notificationsDisplay from './components/notifications';
import snackbars from './components/snackbars/notifications';
@@ -82,6 +83,7 @@ import BuyModal from './components/shops/buyModal.vue';
import SelectMembersModal from 'client/components/selectMembersModal.vue';
import notifications from 'client/mixins/notifications';
import { setup as setupPayments } from 'client/libs/payments';
import amazonPaymentsModal from 'client/components/payments/amazonModal';
export default {
mixins: [notifications],
@@ -94,6 +96,7 @@ export default {
snackbars,
BuyModal,
SelectMembersModal,
amazonPaymentsModal,
},
data () {
return {
@@ -135,12 +138,12 @@ export default {
// @TODO: I'm not sure these should be at the app level. Can we move these back into shop/inventory or maybe they need a lateral move?
this.$root.$on('buyModal::showItem', (item) => {
this.selectedItemToBuy = item;
this.$root.$emit('show::modal', 'buy-modal');
this.$root.$emit('bv::show::modal', 'buy-modal');
});
this.$root.$on('selectMembersModal::showItem', (item) => {
this.selectedSpellToBuy = item;
this.$root.$emit('show::modal', 'select-member-modal');
this.$root.$emit('bv::show::modal', 'select-member-modal');
});
// @TODO split up this file, it's too big
@@ -216,15 +219,11 @@ export default {
// Verify the client is updated
// const serverAppVersion = response.data.appVersion;
// let serverAppVersionState = this.$store.state.serverAppVersion;
// let deniedUpdate = this.$store.state.deniedUpdate;
// if (isApiCall && !serverAppVersionState) {
// this.$store.state.serverAppVersion = serverAppVersion;
// } else if (isApiCall && serverAppVersionState !== serverAppVersion && !deniedUpdate || isCron) {
// // For reload on cron
// if (isCron || confirm(this.$t('habiticaHasUpdated'))) {
// } else if (isApiCall && serverAppVersionState !== serverAppVersion) {
// if (document.activeElement.tagName !== 'INPUT' || confirm(this.$t('habiticaHasUpdated'))) {
// location.reload(true);
// } else {
// this.$store.state.deniedUpdate = true;
// }
// }
@@ -268,7 +267,7 @@ export default {
}
// Manage modals
this.$root.$on('show::modal', (modalId, data = {}) => {
this.$root.$on('bv::show::modal', (modalId, data = {}) => {
if (data.fromRoot) return;
// Track opening of gems modal unless it's been already tracked
@@ -290,15 +289,16 @@ export default {
this.$store.state.modalStack.push(modalId);
// Hide the previous top modal
if (modalOnTop) this.$root.$emit('hide::modal', modalOnTop, {fromRoot: true});
if (modalOnTop) this.$root.$emit('bv::hide::modal', modalOnTop, {fromRoot: true});
});
// @TODO: This part is hacky and could be solved with two options:
// 1 - Find a way to pass fromRoot to hidden
// 2 - Enforce that all modals use the hide::modal event
this.$root.$on('hidden::modal', (modalId) => {
this.$root.$on('bv::modal::hidden', (bvEvent) => {
const modalId = bvEvent.target.id;
let modalStackLength = this.$store.state.modalStack.length;
let modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
let modalSecondToTop = this.$store.state.modalStack[modalStackLength - 2];
// Don't remove modal if hid was called from main app
// @TODO: I'd reather use this, but I don't know how to pass data to hidden event
@@ -310,13 +310,15 @@ export default {
// Recalculate and show the last modal if there is one
modalStackLength = this.$store.state.modalStack.length;
modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
if (modalOnTop) this.$root.$emit('show::modal', modalOnTop, {fromRoot: true});
let modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
if (modalOnTop) this.$root.$emit('bv::show::modal', modalOnTop, {fromRoot: true});
});
},
methods: {
resetItemToBuy ($event) {
if (!$event) {
// @TODO: Do we need this? I think selecting a new item
// overwrites. @negue might know
if (!$event && this.selectedItemToBuy.purchaseType !== 'card') {
this.selectedItemToBuy = null;
}
},
@@ -334,23 +336,21 @@ export default {
},
customPurchase (item) {
if (item.purchaseType === 'card') {
if (this.user.party._id) {
this.selectedSpellToBuy = item;
this.selectedSpellToBuy = item;
this.$root.$emit('hide::modal', 'buy-modal');
this.$root.$emit('show::modal', 'select-member-modal');
} else {
this.error(this.$t('errorNotInParty'));
}
this.$root.$emit('bv::hide::modal', 'buy-modal');
this.$root.$emit('bv::show::modal', 'select-member-modal');
}
},
async memberSelected (member) {
this.$store.dispatch('user:castSpell', {key: this.selectedSpellToBuy.key, targetId: member.id});
this.selectedSpellToBuy = null;
this.$store.dispatch('party:getMembers', {forceLoad: true});
if (this.user.party._id) {
this.$store.dispatch('party:getMembers', {forceLoad: true});
}
this.$root.$emit('hide::modal', 'select-member-modal');
this.$root.$emit('bv::hide::modal', 'select-member-modal');
},
hideLoadingScreen () {
const loadingScreen = document.getElementById('loading-screen');
@@ -361,6 +361,27 @@ export default {
</script>
<style src="intro.js/minified/introjs.min.css"></style>
<style src="bootstrap/scss/bootstrap.scss" lang="scss"></style>
<style src="assets/scss/index.scss" lang="scss"></style>
<style src="assets/css/index.css"></style>
<style src="assets/css/sprites/spritesmith-largeSprites-0.css"></style>
<style src="assets/css/sprites/spritesmith-main-0.css"></style>
<style src="assets/css/sprites/spritesmith-main-1.css"></style>
<style src="assets/css/sprites/spritesmith-main-2.css"></style>
<style src="assets/css/sprites/spritesmith-main-3.css"></style>
<style src="assets/css/sprites/spritesmith-main-4.css"></style>
<style src="assets/css/sprites/spritesmith-main-5.css"></style>
<style src="assets/css/sprites/spritesmith-main-6.css"></style>
<style src="assets/css/sprites/spritesmith-main-7.css"></style>
<style src="assets/css/sprites/spritesmith-main-8.css"></style>
<style src="assets/css/sprites/spritesmith-main-9.css"></style>
<style src="assets/css/sprites/spritesmith-main-10.css"></style>
<style src="assets/css/sprites/spritesmith-main-11.css"></style>
<style src="assets/css/sprites/spritesmith-main-12.css"></style>
<style src="assets/css/sprites/spritesmith-main-13.css"></style>
<style src="assets/css/sprites/spritesmith-main-14.css"></style>
<style src="assets/css/sprites/spritesmith-main-15.css"></style>
<style src="assets/css/sprites/spritesmith-main-16.css"></style>
<style src="assets/css/sprites/spritesmith-main-17.css"></style>
<style src="assets/css/sprites/spritesmith-main-18.css"></style>
<style src="assets/css/sprites/spritesmith-main-19.css"></style>
<style src="assets/css/sprites/spritesmith-main-20.css"></style>
<style src="assets/css/sprites.css"></style>

View File

@@ -1,2 +0,0 @@
@import './sprites/*.css';
@import './sprites.css';

View File

@@ -1,12 +1,12 @@
.promo_mystery_201710 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: 0px -244px;
width: 93px;
height: 90px;
}
.scene_positivity {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
.promo_armoire_backgrounds_201712 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 531px;
height: 243px;
width: 141px;
height: 441px;
}
.promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -142px 0px;
width: 114px;
height: 87px;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,222 +1,834 @@
.Pet-Wolf-Ghost {
background-image: url(/static/sprites/spritesmith-main-20.png);
.Pet-TRex-Base {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -246px -400px;
width: 81px;
height: 99px;
}
.Pet-TRex-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -328px -400px;
width: 81px;
height: 99px;
}
.Pet-TRex-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -410px -400px;
width: 81px;
height: 99px;
}
.Pet-TRex-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -492px -400px;
width: 81px;
height: 99px;
}
.Pet-TRex-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -574px 0px;
width: 81px;
height: 99px;
}
.Pet-TRex-Red {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -574px -100px;
width: 81px;
height: 99px;
}
.Pet-TRex-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -574px -200px;
width: 81px;
height: 99px;
}
.Pet-TRex-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -574px -300px;
width: 81px;
height: 99px;
}
.Pet-TRex-White {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -574px -400px;
width: 81px;
height: 99px;
}
.Pet-TRex-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: 0px -500px;
width: 81px;
height: 99px;
}
.Pet-TigerCub-Base {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -82px 0px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Golden {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -328px -100px;
.Pet-TigerCub-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -738px -600px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Holly {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -246px -100px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Peppermint {
background-image: url(/static/sprites/spritesmith-main-20.png);
.Pet-TigerCub-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -164px 0px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Red {
background-image: url(/static/sprites/spritesmith-main-20.png);
.Pet-TigerCub-Cupid {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: 0px -100px;
width: 81px;
height: 99px;
}
.Pet-Wolf-RoyalPurple {
background-image: url(/static/sprites/spritesmith-main-20.png);
.Pet-TigerCub-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -82px -100px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Shade {
background-image: url(/static/sprites/spritesmith-main-20.png);
.Pet-TigerCub-Ember {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -164px -100px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Shimmer {
background-image: url(/static/sprites/spritesmith-main-20.png);
.Pet-TigerCub-Fairy {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -246px 0px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Skeleton {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: 0px 0px;
.Pet-TigerCub-Floral {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -246px -100px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Spooky {
background-image: url(/static/sprites/spritesmith-main-20.png);
.Pet-TigerCub-Ghost {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: 0px -200px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Thunderstorm {
background-image: url(/static/sprites/spritesmith-main-20.png);
.Pet-TigerCub-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -82px -200px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Veteran {
background-image: url(/static/sprites/spritesmith-main-20.png);
.Pet-TigerCub-Holly {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -164px -200px;
width: 81px;
height: 99px;
}
.Pet-Wolf-White {
background-image: url(/static/sprites/spritesmith-main-20.png);
.Pet-TigerCub-Peppermint {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -246px -200px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Zombie {
background-image: url(/static/sprites/spritesmith-main-20.png);
.Pet-TigerCub-Red {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -328px 0px;
width: 81px;
height: 99px;
}
.Pet_HatchingPotion_Aquatic {
background-image: url(/static/sprites/spritesmith-main-20.png);
.Pet-TigerCub-RoyalPurple {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -328px -100px;
width: 81px;
height: 99px;
}
.Pet-TigerCub-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -328px -200px;
width: 81px;
height: 99px;
}
.Pet-TigerCub-Shimmer {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: 0px -300px;
width: 81px;
height: 99px;
}
.Pet-TigerCub-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -82px -300px;
width: 81px;
height: 99px;
}
.Pet-TigerCub-Spooky {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -164px -300px;
width: 81px;
height: 99px;
}
.Pet-TigerCub-Thunderstorm {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -246px -300px;
width: 81px;
height: 99px;
}
.Pet-TigerCub-White {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -328px -300px;
width: 81px;
height: 99px;
}
.Pet-TigerCub-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -410px 0px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Base {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -410px -100px;
width: 81px;
height: 99px;
}
.Pet-Treeling-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -410px -200px;
width: 81px;
height: 99px;
}
.Pet-Treeling-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -410px -300px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -492px 0px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -492px -100px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Red {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -492px -200px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -492px -300px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: 0px -400px;
width: 81px;
height: 99px;
}
.Pet-Treeling-White {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -82px -400px;
width: 81px;
height: 99px;
}
.Pet-Treeling-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -164px -400px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Base {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -82px -500px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -164px -500px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -246px -500px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -328px -500px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -410px -500px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Red {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -492px -500px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -574px -500px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -656px 0px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-White {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -656px -100px;
width: 81px;
height: 99px;
}
.Pet-Triceratops-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -656px -200px;
width: 81px;
height: 99px;
}
.Pet-Turkey-Base {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -656px -300px;
width: 81px;
height: 99px;
}
.Pet-Turkey-Gilded {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -656px -400px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Base {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -656px -500px;
width: 81px;
height: 99px;
}
.Pet-Turtle-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: 0px -600px;
width: 81px;
height: 99px;
}
.Pet-Turtle-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -82px -600px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -164px -600px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -246px -600px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Red {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -328px -600px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -410px -600px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -492px -600px;
width: 81px;
height: 99px;
}
.Pet-Turtle-White {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -574px -600px;
width: 81px;
height: 99px;
}
.Pet-Turtle-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -656px -600px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Base {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -738px 0px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -738px -100px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -738px -200px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -738px -300px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -738px -400px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Red {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -738px -500px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: 0px 0px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: 0px -700px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-White {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -82px -700px;
width: 81px;
height: 99px;
}
.Pet-Unicorn-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -164px -700px;
width: 81px;
height: 99px;
}
.Pet-Whale-Base {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -246px -700px;
width: 81px;
height: 99px;
}
.Pet-Whale-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -328px -700px;
width: 81px;
height: 99px;
}
.Pet-Whale-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -410px -700px;
width: 81px;
height: 99px;
}
.Pet-Whale-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -492px -700px;
width: 81px;
height: 99px;
}
.Pet-Whale-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -574px -700px;
width: 81px;
height: 99px;
}
.Pet-Whale-Red {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -656px -700px;
width: 81px;
height: 99px;
}
.Pet-Whale-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -738px -700px;
width: 81px;
height: 99px;
}
.Pet-Whale-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -820px 0px;
width: 81px;
height: 99px;
}
.Pet-Whale-White {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -820px -100px;
width: 81px;
height: 99px;
}
.Pet-Whale-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -820px -200px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Aquatic {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -820px -300px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Base {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -820px -400px;
width: 81px;
height: 99px;
}
.Pet-Wolf-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -820px -500px;
width: 81px;
height: 99px;
}
.Pet-Wolf-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -820px -600px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Cupid {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -820px -700px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: 0px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Ember {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -82px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Fairy {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -164px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Floral {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -246px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Ghost {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -328px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -410px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Holly {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -492px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Peppermint {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -574px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Red {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -656px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-RoyalPurple {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -738px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -820px -800px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Shimmer {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -902px 0px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -902px -100px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Spooky {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -902px -200px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Thunderstorm {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -902px -300px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Veteran {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -902px -400px;
width: 81px;
height: 99px;
}
.Pet-Wolf-White {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -902px -500px;
width: 81px;
height: 99px;
}
.Pet-Wolf-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -902px -600px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Base {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -902px -700px;
width: 81px;
height: 99px;
}
.Pet-Yarn-CottonCandyBlue {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -902px -800px;
width: 81px;
height: 99px;
}
.Pet-Yarn-CottonCandyPink {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -984px 0px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Desert {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -984px -100px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Golden {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -984px -200px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Red {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -984px -300px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Shade {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -984px -400px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Skeleton {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -984px -500px;
width: 81px;
height: 99px;
}
.Pet-Yarn-White {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -984px -600px;
width: 81px;
height: 99px;
}
.Pet-Yarn-Zombie {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -984px -700px;
width: 81px;
height: 99px;
}
.Pet_HatchingPotion_Aquatic {
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: 0px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Base {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: 0px -300px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -690px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_CottonCandyBlue {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -69px -300px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -69px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_CottonCandyPink {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -138px -300px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -138px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Cupid {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -479px -345px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -207px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Desert {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -276px -300px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -276px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Ember {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -410px 0px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -345px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Fairy {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -410px -69px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -414px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Floral {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -410px -138px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -483px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Ghost {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -410px -207px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -552px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Golden {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -410px -276px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -621px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Holly {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: 0px -369px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -984px -800px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Peppermint {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -69px -369px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -759px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Purple {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -138px -369px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -828px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Red {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -207px -369px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -897px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_RoyalPurple {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -276px -369px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -966px -900px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Shade {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -345px -369px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: 0px -969px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Shimmer {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -479px 0px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -69px -969px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Skeleton {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -479px -69px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -138px -969px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Spooky {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -479px -138px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -207px -969px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Thunderstorm {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -479px -207px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -276px -969px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_White {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -479px -276px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -345px -969px;
width: 68px;
height: 68px;
}
.Pet_HatchingPotion_Zombie {
background-image: url(/static/sprites/spritesmith-main-20.png);
background-position: -207px -300px;
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
background-position: -414px -969px;
width: 68px;
height: 68px;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

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