Compare commits

..

33 Commits

Author SHA1 Message Date
Sabe Jones
fd8120c80d 4.0.9 2017-09-30 17:38:49 +00:00
Sabe Jones
053e75562f Merge branch 'develop' into release 2017-09-30 17:38:34 +00:00
Sabe Jones
1d50027f51 fix(challenges): Markdown titles 2017-09-30 17:10:57 +00:00
Sabe Jones
7fe74fd06a Serve static news page for mobile (#9109)
* fix(news): serve static page for mobile

* fix(news): commit new component

* refactor(news): computed prop DRY
2017-09-30 10:48:24 -05:00
Sabe Jones
173a8561b6 4.0.8 2017-09-30 13:37:29 +00:00
Sabe Jones
f21bef707b Merge branch 'develop' into release 2017-09-30 13:36:40 +00:00
Keith Holliday
9cf2ccf7c4 Style fixes (#9108)
* fixed some columns on smaller screens

* changed mystic hourglass check

* Checked for bad data

* Groupped pets and mounts

* Added show more

* Fixed lint

* Fixed lint
2017-09-30 08:34:25 -05:00
Keith Holliday
77d75c4669 Pending quest styles (#9106)
* Added pending quest modal

* Added quest completed modal

* Removed duplicate string
2017-09-30 08:28:39 -05:00
thehollidayinn
1c17b95146 4.0.7 2017-09-29 20:18:00 -05:00
Trevor Ford
d89fc209d1 fix allocate() function in (#9105)
website/client/components/userMenu/profile.vue
2017-09-29 18:48:24 -05:00
Sabe Jones
844c8bb3e6 4.0.6 2017-09-29 22:08:23 +00:00
Sabe Jones
569fb11db8 Merge branch 'develop' into release 2017-09-29 22:08:11 +00:00
Keith Holliday
7671347d3a Chat avatar fixes (#9103)
* Ensured rejection doesn't hurt performance

* Added debounce and scroll removal

* Added debounce for keydown

* Fixed linting
2017-09-29 16:03:43 -05:00
Alys
dc3a02bc2e clarify change class code and remove incorrect confirmation step (#9099)
- prevent the user seeing a confirmation about paying 3 gems when enabling the class system (which is free)
- rename changeClass function to changeClassForUser to avoid confusion with changeClass function
- make change class confirmation translatable
- changed "gems" to "Gems" in locales string
2017-09-29 15:57:05 -05:00
Alys
1d8c126687 hide the Block button in the user's own profile screen (#9098)
This prevents a user from accidentally blocking themself from sending PMs.
2017-09-29 15:56:54 -05:00
Alys
7ee49a43f2 Various fixes: mods can delete any message; no Report / CopyAsTodo button in inbox; etc (#9091)
* fix link to Data Display Tool

* prevent the Copy As To-Do button appearing in the inbox because it isn't working

* allow mods/staff to delete any chat message

* prevent the Flag/Report button appearing in the inbox because it isn't working
2017-09-29 15:56:44 -05:00
Sabe Jones
900bc8dfc1 Merge branch 'develop' into release 2017-09-29 20:34:52 +00:00
Keith Holliday
ec260016d3 Task sort payment fixes (#9104)
* Added sorting to dated filter

* Validated payment data type
2017-09-29 15:30:28 -05:00
Sabe Jones
ec770fb29e 4.0.5 2017-09-29 16:19:02 +00:00
Matteo Pagliazzi
c1079e4eae Client Tasks Length (#9101)
* change tasks column height

* stack columns on smaller screens
2017-09-29 18:17:58 +02:00
Sabe Jones
3cb5637fd5 Merge branch 'release' into develop 2017-09-29 15:21:10 +00:00
Matteo Pagliazzi
cf3a0118c9 fix typo 2017-09-29 12:39:18 +02:00
Matteo Pagliazzi
895a383089 cache what is cacheable (because hashed) in the new client, should fix the broken navigation while using the site before and after a deploy 2017-09-29 12:37:03 +02:00
Matteo Pagliazzi
f730e7b345 fix password reset (setting a new one) 2017-09-29 12:15:31 +02:00
Alys
d8f3d99d59 fix footer link to Report a Bug guild 2017-09-29 18:57:33 +10:00
Sabe Jones
99fb1f6116 4.0.4 2017-09-29 01:56:18 +00:00
Alys
7c6dce2124 change link in Bailey from G docs to wiki 2017-09-29 11:51:50 +10:00
Sabe Jones
c757a3f52d 4.0.3 2017-09-28 23:50:44 +00:00
Sabe Jones
ffb318fe8d fix(news): sprite path 2017-09-28 23:50:29 +00:00
Sabe Jones
19cd15ed62 4.0.2 2017-09-28 23:37:16 +00:00
Sabe Jones
3495662196 fix(redesign): Bailey and debug 2017-09-28 23:37:00 +00:00
SabreCat
908a1340a4 4.0.1 2017-09-28 22:59:07 +00:00
Keith Holliday
31f4610b20 Sabrecat/redesign launch bailey (#9094)
* chore(news): Bailey

* chore(news): Bailey on old client

* Fixed seasonal images

* Fixed footer and images for bailey
2017-09-28 15:36:48 -05:00
34 changed files with 652 additions and 276 deletions

View File

@@ -9,7 +9,7 @@ RUN npm install -g gulp grunt-cli bower mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone --branch release https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN git clone --branch v4.0.3 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN bower install --allow-root
RUN gulp build:prod --force

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "habitica",
"version": "4.0.1",
"version": "4.0.9",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.0.1",
"version": "4.0.9",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",

View File

@@ -138,12 +138,21 @@ export default {
// @TODO split up this file, it's too big
// Set up Error interceptors
axios.interceptors.response.use((response) => {
if (this.user) {
if (this.user && response.data && response.data.notifications) {
this.$set(this.user, 'notifications', response.data.notifications);
}
return response;
}, (error) => {
if (error.response.status >= 400) {
// Don't show errors from getting user details. These users have delete their account,
// but their chat message still exists.
let configExists = Boolean(error.response) && Boolean(error.response.config);
if (configExists && error.response.config.method === 'get' && error.response.config.url.indexOf('/api/v3/members/') !== -1) {
// @TODO: We resolve the promise because we need our caching to cache this user as tried
// Chat paging should help this, but maybe we can also find another solution..
return Promise.resolve(error);
}
this.$store.state.notificationStore.push({
title: 'Habitica',
text: error.response.data.message,

View File

@@ -6,35 +6,7 @@
:hide-footer='true',
)
.modal-body
.media
.align-self-center.right-margin(:class='baileyClass')
.media-body
h1.align-self-center(v-markdown='$t("newStuff")')
h2 9/28/2017 - HABITICA'S WEBSITE LEVELS UP!
hr
p Welcome to the new Habitica! We're so excited to share it with you at last. This project, which has been a labor of love since last December, is the single biggest update that Habitica has ever released (with over 150 pages of designs, an entire rewrite of all of our front-end code, countless rounds of testing and iteration, and many, many meetings). Just refresh your page to load the new website!
.promo_login_screen.center-block
p(v-markdown="'You can find a full list of changes [here](https://docs.google.com/document/d/1GZ0A2MK3JCZPuFUOStR7aVUI8fQlmjFDtLdj5Lg5XHY/edit), as well as explanations for why we made each, but here are a few quick tips to help you get oriented:'")
.seasonal-shop-backdrop
.daniel_front
ul
li There's a ton of new art around the site! Peek at the NPCs and Guild chats to admire some of the changes.
li Click directly on your tasks to bring up the edit modal!
li The navigation bar contains several changes to be more intuitive for new users, so we recommend taking some time to open the drop-down menus and familiarize yourself with the new locations. Notably, the User menu has moved to an icon in the upper-right corner.
li You can now pin any purchasable item in the game to your Rewards. You can pin Backgrounds, too! Just hover over the shop icon and click the pin. When you head back to the tasks page, you'll see it in your Rewards column!
li There are lots of new filtering options, especially for Guilds and Challenges!
li There are visual upgrades for every aspect of the site, from the front page to the Seasonal Shop. We hope that you like them!
li Some of these upgrades have made their way to our <a href='https://itunes.apple.com/us/app/habitica/id994882113?ls=1&mt=8' target='_blank'>iOS</a> and <a href='https://play.google.com/store/apps/details?id=com.habitrpg.android.habitica' target='_blank'>Android</a> apps! Be sure to download the latest updates for the best performance.
.seasonal-shop-backdrop
.sorceress_front
p Have general questions about how the new site works? Come ask in the <a href='https://habitica.com/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a'>Habitica Help Guild</a>, and we'll be glad to assist! Likewise, if you encounter a persistent bug that isn't fixed by refreshing your page, you can report it in the <a href='https://habitica.com/groups/guild/a29da26b-37de-4a71-b0c6-48e72a900dac'>Report a Bug Guild</a> and we will investigate as soon as possible.
p If you have thoughts about the new design, we look forward to hearing them. On <strong>October 12th</strong> we will be opening a Trello card for public comments on the redesign. This delay will give us time to focus our attention on answering general questions and fixing any bugs that might arise. For this reason, we ask that you hold back on sharing your feedback about the new designs until that Trello card is live and linked in a Bailey announcement. Thanks for understanding!
.promo_veteran_pets_2017.center-block
p This is a major time of change for Habitica, so to thank you for your patience, we've given everyone a Veteran Pet! Newer users have received a Veteran Wolf, and older users have received (depending on which pets they already had) a Veteran Tiger, a Veteran Lion, or a Veteran Bear. Head to the new <a href='https://habitica.com/inventory/stable'>Stable</a> page and filter to the Special Pets section to see the latest addition to your menagerie!"')
p We are so excited to continue to build Habitica with you. Now go check it out!
p Thank you for playing, and good luck with your tasks <3
.small by Apollo, piyorii, TheHollidayInn, Paglias, Negue, Sabe, Alys, viirus, Lemoness, redphoenix, beffymaroo, and all our awesome testers!
br
new-stuff
.modal-footer
a.btn.btn-info(href='http://habitica.wikia.com/wiki/Whats_New', target='_blank') {{ this.$t('newsArchive') }}
button.btn.btn-default(@click='close()') {{ this.$t('cool') }}
@@ -44,86 +16,36 @@
<style lang='scss' scoped>
@import '~client/assets/scss/static.scss';
.center-block {
margin: 0 auto;
}
.grassy-meadow-backdrop {
background-image: url('~assets/images/npc/normal/tavern_background.png');
background-repeat: repeat-x;
width: 100%;
height: 246px;
}
.daniel_front {
background-image: url('~assets/images/npc/fall/seasonal_shop_opened_npc.png');
height: 246px;
width: 471px;
background-repeat: no-repeat;
margin: 0 auto;
}
.seasonal-shop-backdrop {
background: url('~assets/images/npc/fall/seasonal_shop_opened_background.png');
background-repeat: repeat-x;
}
.sorceress_front {
background-image: url('~assets/images/npc/normal/tavern_npc.png');
height: 246px;
width: 471px;
background-repeat: no-repeat;
margin: 0 auto;
}
.modal-body {
padding-top: 2em;
}
.left-margin {
margin-left: 1em;
}
.right-margin {
margin-right: 1em;
}
</style>
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
import { mapState } from 'client/libs/store';
import markdown from 'client/directives/markdown';
import bModal from 'bootstrap-vue/lib/components/modal';
import { mapState } from 'client/libs/store';
import markdown from 'client/directives/markdown';
import newStuff from 'client/components/static/newStuff';
export default {
components: {
bModal,
},
computed: {
...mapState({user: 'user.data'}),
},
data () {
let worldDmg = {
bailey: false,
};
return {
baileyClass: {
'npc_bailey_broken': worldDmg.bailey, // eslint-disable-line
'npc_bailey': !worldDmg.bailey, // eslint-disable-line
export default {
components: {
bModal,
newStuff,
},
computed: {
...mapState({user: 'user.data'}),
},
directives: {
markdown,
},
methods: {
close () {
this.$root.$emit('hide::modal', 'new-stuff');
},
dismissAlert () {
this.$store.dispatch('user:set', {'flags.newStuff': false});
this.close();
},
};
},
directives: {
markdown,
},
methods: {
close () {
this.$root.$emit('hide::modal', 'new-stuff');
},
dismissAlert () {
this.$store.dispatch('user:set', {'flags.newStuff': false});
this.close();
},
},
};
};
</script>

View File

@@ -1,14 +1,20 @@
<template lang="pug">
b-modal#quest-completed(v-if='user.party.quest.completed', :title="quests[user.party.quest.completed].text() + '' + $t('completed')",
size='lg', :hide-footer="true")
b-modal#quest-completed(v-if='user.party.quest.completed', :title="title",
size='md', :hide-footer="true")
.modal-body.text-center
div(:class='`quest_${user.party.quest.completed}`')
p(v-html='quests[user.party.quest.completed].completion()')
.quest(:class='`quest_${user.party.quest.completed}`')
p(v-html='this.questData.completion()')
.quest-rewards(key='user.party.quest.completed', header-participant="$t('youReceived')", header-quest-owner="$t('questOwnerReceived')")
.modal-footer
button.btn.btn-primary(@click='setQuestCompleted()') {{ $t('ok') }}
</template>
<style scoped>
.quest {
margin: 0 auto;
}
</style>
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
import quests from 'common/script/content/quests';
@@ -29,6 +35,12 @@ export default {
},
computed: {
...mapState({user: 'user.data'}),
questData () {
return this.quests.quests[this.user.party.quest.completed];
},
title () {
return `${this.questData.text()} ${this.$t('completed')}`;
},
barStyle () {
return {
width: `${percent(this.user.stats.hp, maxHealth)}%`,

View File

@@ -4,12 +4,12 @@
modify-inventory(v-if="isUserLoaded")
footer.container-fluid
.row
.col-2
.col-12.col-md-2
h3
a(href='https://itunes.apple.com/us/app/habitica/id994882113?ls=1&mt=8', target='_blank') {{ $t('mobileIOS') }}
h3
a(href='https://play.google.com/store/apps/details?id=com.habitrpg.android.habitica', target='_blank') {{ $t('mobileAndroid') }}
.col-2
.col-12.col-md-2
h3 Company
ul
li
@@ -28,7 +28,7 @@
a(href='/static/press-kit') {{ $t('presskit') }}
li
a(href='/static/contact') {{ $t('contactUs') }}
.col-2
.col-12.col-md-2
h3 Community
ul
li
@@ -36,7 +36,7 @@
li
router-link(to='/hall/contributors') {{ $t('hall') }}
li
router-link(to='/groups/a29da26b-37de-4a71-b0c6-48e72a900dac') {{ $t('reportBug') }}
router-link(to='/groups/guild/a29da26b-37de-4a71-b0c6-48e72a900dac') {{ $t('reportBug') }}
li
a(href='https://trello.com/c/odmhIqyW/440-read-first-table-of-contents', target='_blank') {{ $t('requestFeature') }}
li
@@ -47,7 +47,7 @@
a(href='https://www.facebook.com/Habitica', target='_blank') {{ $t('communityFacebook') }}
li
a(href='https://www.reddit.com/r/habitrpg/', target='_blank') {{ $t('communityReddit') }}
.col-6
.col-12.col-md-6
.row
.col-6
h3 Developers
@@ -55,7 +55,7 @@
li
a(href='/apidoc', target='_blank') {{ $t('APIv3') }}
li
a(href='http://data.habitrpg.com/?uuid=', target='_blank') {{ $t('dataDisplayTool') }}
a(href='https://oldgods.net/habitrpg/habitrpg_user_data_display.html', target='_blank') {{ $t('dataDisplayTool') }}
li
a(href='http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths', target='_blank') {{ $t('guidanceForBlacksmiths') }}
li
@@ -82,7 +82,7 @@
.row
.col-4
| © 2017 Habitica. All rights reserved.
.debug.float-left(v-if="!IS_PRODUCTION && isUserLoaded")
// .debug.float-left(v-if="!IS_PRODUCTION && isUserLoaded")
button.btn.btn-primary(@click="debugMenuShown = !debugMenuShown") Toggle Debug Menu
.debug-group(v-if="debugMenuShown")
a.btn.btn-default(@click="setHealthLow()") Health = 1

View File

@@ -52,7 +52,7 @@ div
a.dropdown-item(href="https://trello.com/c/odmhIqyW/440-read-first-table-of-contents", target='_blank') {{ $t('requestAF') }}
a.dropdown-item(href="http://habitica.wikia.com/wiki/Contributing_to_Habitica", target='_blank') {{ $t('contributing') }}
a.dropdown-item(href="http://habitica.wikia.com/wiki/Habitica_Wiki", target='_blank') {{ $t('wiki') }}
.item-with-icon(v-if="userHourglasses != 0")
.item-with-icon(v-if="userHourglasses > 0")
.svg-icon(v-html="icons.hourglasses")
span {{ userHourglasses }}
.item-with-icon

View File

@@ -485,14 +485,14 @@ export default {
return;
}
const res = await axios.post('/api/v3/user/reset-password', {
const res = await axios.post('/api/v3/user/auth/reset-password-set-new-one', {
newPassword: this.password,
confirmPassword: this.passwordConfirm,
code: this.resetPasswordSetNewOneData.code,
});
if (res.message) {
alert(res.message);
if (res.data.message) {
alert(res.data.message);
}
this.password = '';

View File

@@ -51,7 +51,7 @@
)
.row
task-column.col-6(
task-column.col-12.col-sm-6(
v-for="column in columns",
:type="column",
:key="column",

View File

@@ -2,7 +2,7 @@
.card
.row
router-link.col-12(:to="{ name: 'challenge', params: { challengeId: challenge._id } }")
h3 {{challenge.name}}
h3(v-markdown='challenge.name')
.row
.col-6
div.details
@@ -163,6 +163,7 @@ import habitIcon from 'assets/svg/habit.svg';
import todoIcon from 'assets/svg/todo.svg';
import dailyIcon from 'assets/svg/daily.svg';
import rewardIcon from 'assets/svg/reward.svg';
import markdownDirective from 'client/directives/markdown';
export default {
props: ['challenge'],
@@ -179,5 +180,8 @@ export default {
}),
};
},
directives: {
markdown: markdownDirective,
},
};
</script>

View File

@@ -16,7 +16,7 @@
.svg-icon.positive-icon(v-html="icons.positiveIcon")
span(v-once) {{$t('createChallenge')}}
.row
.col-6(v-for='challenge in filteredChallenges', v-if='!memberOf(challenge)')
.col-12.col-md-6(v-for='challenge in filteredChallenges', v-if='!memberOf(challenge)')
challenge-item(:challenge='challenge')
</template>

View File

@@ -23,7 +23,7 @@
p(v-once) {{$t('challengeDescription2')}}
.row
.col-6(v-for='challenge in filteredChallenges')
.col-12.col-md-6(v-for='challenge in filteredChallenges')
challenge-item(:challenge='challenge')
</template>

View File

@@ -1,5 +1,5 @@
<template lang="pug">
.col-2.standard-sidebar
.col-2.standard-sidebar.hidden-xs-down
.form-group
input.form-control.search(type="text", :placeholder="$t('search')", v-model='searchTerm')

View File

@@ -5,14 +5,14 @@
copy-as-todo-modal(:copying-message='copyingMessage', :group-name='groupName', :group-id='groupId')
report-flag-modal
div(v-for="(msg, index) in chat", v-if='chat && canViewFlag(msg)')
div(v-for="(msg, index) in messages", v-if='chat && canViewFlag(msg)')
// @TODO: is there a different way to do these conditionals? This creates an infinite loop
//.hr(v-if='displayDivider(msg)')
.hr-middle(v-once) {{ msg.timestamp }}
.row(v-if='user._id !== msg.uuid')
div(:class='inbox ? "col-4" : "col-2"')
avatar(
v-if='cachedProfileData[msg.uuid]',
v-if='cachedProfileData[msg.uuid] && !cachedProfileData[msg.uuid].rejected',
:member="cachedProfileData[msg.uuid]",
:avatarOnly="true",
:hideClassBadge='true',
@@ -36,13 +36,15 @@
.svg-icon(v-html="icons.like")
span(v-if='!msg.likes[user._id]') {{ $t('like') }}
span(v-if='msg.likes[user._id]') {{ $t('liked') }}
span.action( @click='copyAsTodo(msg)')
span.action(v-if='!inbox', @click='copyAsTodo(msg)')
.svg-icon(v-html="icons.copy")
| {{$t('copyAsTodo')}}
span.action(v-if='user.contributor.admin || (msg.uuid !== user._id && user.flags.communityGuidelinesAccepted)', @click='report(msg)')
// @TODO make copyAsTodo work in the inbox
span.action(v-if='!inbox && user.flags.communityGuidelinesAccepted', @click='report(msg)')
.svg-icon(v-html="icons.report")
| {{$t('report')}}
span.action(v-if='msg.uuid === user._id || inbox', @click='remove(msg, index)')
// @TODO make flagging/reporting work in the inbox. NOTE: it must work even if the communityGuidelines are not accepted and it MUST work for messages that you have SENT as well as received. -- Alys
span.action(v-if='msg.uuid === user._id || inbox || user.contributor.admin', @click='remove(msg, index)')
.svg-icon(v-html="icons.delete")
| {{$t('delete')}}
span.action.float-right.liked(v-if='likeCount(msg) > 0')
@@ -70,12 +72,15 @@
.svg-icon(v-html="icons.like")
span(v-if='!msg.likes[user._id]') {{ $t('like') }}
span(v-if='msg.likes[user._id]') {{ $t('liked') }}
span.action( @click='copyAsTodo(msg)')
span.action(v-if='!inbox', @click='copyAsTodo(msg)')
.svg-icon(v-html="icons.copy")
| {{$t('copyAsTodo')}}
// @TODO make copyAsTodo work in the inbox
span.action(v-if='user.flags.communityGuidelinesAccepted', @click='report(msg)')
span.action(v-if='!inbox && user.flags.communityGuidelinesAccepted', @click='report(msg)')
.svg-icon(v-html="icons.report")
| {{$t('report')}}
// @TODO make flagging/reporting work in the inbox. NOTE: it must work even if the communityGuidelines are not accepted and it MUST work for messages that you have SENT as well as received. -- Alys
span.action(v-if='msg.uuid === user._id', @click='remove(msg, index)')
.svg-icon(v-html="icons.delete")
| {{$t('delete')}}
@@ -84,7 +89,7 @@
| + {{ likeCount(msg) }}
div(:class='inbox ? "col-4" : "col-2"')
avatar(
v-if='cachedProfileData[msg.uuid]',
v-if='cachedProfileData[msg.uuid] && !cachedProfileData[msg.uuid].rejected',
:member="cachedProfileData[msg.uuid]",
:avatarOnly="true",
:hideClassBadge='true',
@@ -238,7 +243,7 @@ import axios from 'axios';
import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep';
import { mapState } from 'client/libs/store';
import throttle from 'lodash/throttle';
import debounce from 'lodash/debounce';
import markdownDirective from 'client/directives/markdown';
import Avatar from '../avatar';
import styleHelper from 'client/mixins/styleHelper';
@@ -277,12 +282,10 @@ export default {
this.loadProfileCache();
},
created () {
window.addEventListener('scroll', throttle(() => {
this.loadProfileCache(window.scrollY / 1000);
}, 1000));
window.addEventListener('scroll', this.handleScroll);
},
destroyed () {
// window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('scroll', this.handleScroll);
},
data () {
return {
@@ -308,6 +311,7 @@ export default {
cachedProfileData: {},
currentProfileLoadedCount: 0,
currentProfileLoadedEnd: 10,
loading: false,
};
},
filters: {
@@ -321,17 +325,22 @@ export default {
},
computed: {
...mapState({user: 'user.data'}),
// @TODO: We need a different lazy load mechnism.
// But honestly, adding a paging route to chat would solve this
messages () {
return this.chat;
},
},
watch: {
messages () {
// @TODO: MAybe we should watch insert and remove?
messages (oldValue, newValue) {
if (newValue.length === oldValue.length) return;
this.loadProfileCache();
},
},
methods: {
handleScroll () {
this.loadProfileCache(window.scrollY / 1000);
},
isUserMentioned (message) {
let user = this.user;
@@ -358,7 +367,10 @@ export default {
if (!message.flagCount || message.flagCount < 2) return true;
return this.user.contributor.admin;
},
async loadProfileCache (screenPosition) {
loadProfileCache: debounce(function loadProfileCache (screenPosition) {
this._loadProfileCache(screenPosition);
}, 1000),
async _loadProfileCache (screenPosition) {
let promises = [];
// @TODO: write an explination
@@ -368,11 +380,10 @@ export default {
return;
}
// @TODO: Not sure we need this hash
let aboutToCache = {};
this.messages.forEach(message => {
let uuid = message.uuid;
if (uuid && !this.cachedProfileData[uuid] && !aboutToCache[uuid]) {
if (Boolean(uuid) && !this.cachedProfileData[uuid] && !aboutToCache[uuid]) {
if (uuid === 'system' || this.currentProfileLoadedCount === this.currentProfileLoadedEnd) return;
aboutToCache[uuid] = {};
promises.push(axios.get(`/api/v3/members/${uuid}`));
@@ -382,9 +393,21 @@ export default {
let results = await Promise.all(promises);
results.forEach(result => {
// We could not load the user. Maybe they were deleted. So, let's cache empty so we don't try again
if (!result || !result.data || result.status >= 400) {
return;
}
let userData = result.data.data;
this.$set(this.cachedProfileData, userData._id, userData);
});
// Merge in any attempts that were rejected so we don't attempt again
for (let uuid in aboutToCache) {
if (!this.cachedProfileData[uuid]) {
this.$set(this.cachedProfileData, uuid, {rejected: true});
}
}
},
displayDivider (message) {
if (this.currentDayDividerDisplay !== moment(message.timestamp).day()) {

View File

@@ -72,7 +72,7 @@
v-on:taskEdited='taskEdited',
)
.row
task-column.col-3(
task-column.col-12.col-sm-6.col-3(
v-for="column in columns",
:type="column",
:key="column",

View File

@@ -3,7 +3,8 @@
group-form-modal(v-if='isParty')
invite-modal(:group='this.group')
start-quest-modal(:group='this.group')
.col-8.standard-page
quest-details-modal(:group='this.group')
.col-12.col-sm-8.standard-page
.row
.col-6.title-details
h1 {{group.name}}
@@ -45,7 +46,7 @@
.col-12.hr
chat-message(:chat.sync='group.chat', :group-id='group._id', group-name='group.name')
.col-4.sidebar
.col-12.col-sm-4.sidebar
.row(:class='{"guild-background": !isParty}')
.col-6
.col-6
@@ -80,9 +81,13 @@
p(v-once) {{ $t('questDescription') }}
button.btn.btn-secondary(v-once, @click="openStartQuestModal()", v-if='isLeader') {{ $t('startAQuest') }}
.row.quest-active-section(v-if='isParty && onPendingQuest && !onActiveQuest')
h2 Pending quest
button.btn.btn-secondary(v-once, @click="questForceStart()") {{ $t('begin') }}
button.btn.btn-secondary(v-once, @click="questCancel()") {{ $t('cancel') }}
.col-2
.quest(:class='`inventory_quest_scroll_${questData.key}`')
.col-6.titles
strong {{ questData.text() }}
p {{acceptedCount}} / {{group.memberCount}}
.col-4
button.btn.btn-secondary(@click="openQuestDetails()") {{ $t('details') }}
.row.quest-active-section(v-if='isParty && !onPendingQuest && onActiveQuest')
.col-12.text-center
.quest-boss(:class="'quest_' + questData.key")
@@ -353,6 +358,10 @@
}
.quest-active-section {
.titles {
padding-top: .5em;
}
.quest-box {
background-image: url('~client/assets/svg/for-css/quest-border.svg');
background-size: 100% 100%;
@@ -431,6 +440,9 @@
</style>
<script>
// @TODO: Break this down into components
import debounce from 'lodash/debounce';
import extend from 'lodash/extend';
import groupUtilities from 'client/mixins/groupsUtilities';
import styleHelper from 'client/mixins/styleHelper';
@@ -438,6 +450,7 @@ import { mapState } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import membersModal from './membersModal';
import startQuestModal from './startQuestModal';
import questDetailsModal from './questDetailsModal';
import quests from 'common/script/content/quests';
import percent from 'common/script/libs/percent';
import groupFormModal from './groupFormModal';
@@ -481,6 +494,7 @@ export default {
inviteModal,
groupChallenges,
autocomplete,
questDetailsModal,
},
directives: {
bToggle,
@@ -522,6 +536,17 @@ export default {
},
computed: {
...mapState({user: 'user.data'}),
acceptedCount () {
let count = 0;
if (!this.group || !this.group.quest) return count;
for (let uuid in this.group.quest.members) {
if (this.group.quest.members[uuid]) count += 1;
}
return count;
},
communityGuidelinesAccepted () {
return this.user.flags.communityGuidelinesAccepted;
},
@@ -655,7 +680,10 @@ export default {
};
document.body.removeChild(div);
},
updateCarretPosition (eventUpdate) {
updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
_updateCarretPosition (eventUpdate) {
if (eventUpdate.metaKey && eventUpdate.keyCode === 13) {
this.sendMessage();
return;
@@ -717,6 +745,9 @@ export default {
openStartQuestModal () {
this.$root.$emit('show::modal', 'start-quest-modal');
},
openQuestDetails () {
this.$root.$emit('show::modal', 'quest-details');
},
checkForAchievements () {
// Checks if user's party has reached 2 players for the first time.
if (!this.user.achievements.partyUp && this.group.memberCount >= 2) {
@@ -811,11 +842,6 @@ export default {
this.$store.state.profileOptions.startingPage = 'profile';
this.$root.$emit('show::modal', 'profile');
},
async questCancel () {
if (!confirm(this.$t('sureCancel'))) return;
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/cancel'});
this.group.quest = quest;
},
async questAbort () {
if (!confirm(this.$t('sureAbort'))) return;
if (!confirm(this.$t('doubleSureAbort'))) return;
@@ -831,10 +857,6 @@ export default {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/accept'});
this.group.quest = quest;
},
async questForceStart () {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/force-start'});
this.group.quest = quest;
},
// @TODO: Move to notificaitons component?
async questReject () {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/reject'});

View File

@@ -0,0 +1,210 @@
<template lang="pug">
b-modal#quest-details(title="Empty", size='md', :hide-footer="true", :hide-header="true")
.left-panel.content
h3.text-center {{ $t('participantsTitle') }}
.row
.col-10.offset-1.text-center
span.description(v-once) {{ $t('participantDesc') }}
.row
.col-12.member(v-for='member in members')
strong(:class="{'declined-name': member.accepted === false}") {{member.name}}
.accepted.float-right(v-if='member.accepted === true') {{ $t('accepted') }}
.declined.float-right(v-if='member.accepted === false') {{ $t('declined') }}
.pending.float-right(v-if='member.accepted === null') {{ $t('pending') }}
div(v-if='questData')
questDialogContent(:item="questData")
div.text-center.actions
div
button.btn.btn-secondary(v-once, @click="questForceStart()") {{ $t('begin') }}
div
.cancel(v-once, @click="questCancel()") {{ $t('cancel') }}
.side-panel(v-if='questData')
questDialogDrops(:item="questData")
</template>
<style lang='scss' scoped>
@import '~client/assets/scss/colors.scss';
header {
background-color: $white !important;
border: none !important;
h5 {
text-indent: -99999px;
}
}
.quest-details {
margin: 0 auto;
text-align: left;
width: 180px;
}
.btn-primary {
margin: 1em 0;
}
.left-panel {
background: #4e4a57;
color: $white;
position: absolute;
height: 460px;
width: 320px;
top: 2.5em;
left: -22em;
z-index: -1;
padding: 2em;
overflow: scroll;
h3 {
color: $white;
}
.selected .quest-wrapper {
border: solid 1.5px #9a62ff;
}
.quest-wrapper:hover {
cursor: pointer;
}
.quest-col .quest-wrapper {
background: $white;
padding: .2em;
margin-bottom: 1em;
border-radius: 3px;
}
.description {
text-align: center;
color: #a5a1ac;
font-size: 12px;
}
}
.side-panel {
position: absolute;
right: -350px;
top: 25px;
border-radius: 8px;
background-color: $gray-600;
box-shadow: 0 2px 16px 0 rgba(26, 24, 29, 0.32);
display: flex;
align-items: center;
flex-direction: column;
width: 364px;
z-index: -1;
height: 93%;
}
.member {
padding: 1em .5em;
border-top: 1px solid #686274;
.declined-name {
color: #878190;
}
.accepted {
color: #1ed3a0;
}
.declined {
color: #f19595;
}
.pending {
color: #c3c0c7;
}
}
.actions {
padding-top: 2em;
padding-bottom: 2em;
.cancel {
color: #f74e52;
margin-top: 3em;
}
.cancel:hover {
cursor: pointer;
}
}
</style>
<script>
import { mapState } from 'client/libs/store';
import bModal from 'bootstrap-vue/lib/components/modal';
import quests from 'common/script/content/quests';
import copyIcon from 'assets/svg/copy.svg';
import greyBadgeIcon from 'assets/svg/grey-badge.svg';
import qrCodeIcon from 'assets/svg/qrCode.svg';
import facebookIcon from 'assets/svg/facebook.svg';
import twitterIcon from 'assets/svg/twitter.svg';
import starIcon from 'assets/svg/star.svg';
import goldIcon from 'assets/svg/gold.svg';
import difficultyStarIcon from 'assets/svg/difficulty-star.svg';
import questDialogDrops from '../shops/quests/questDialogDrops';
import questDialogContent from '../shops/quests/questDialogContent';
export default {
props: ['group'],
components: {
bModal,
questDialogDrops,
questDialogContent,
},
data () {
return {
loading: false,
selectedQuest: {},
icons: Object.freeze({
copy: copyIcon,
greyBadge: greyBadgeIcon,
qrCode: qrCodeIcon,
facebook: facebookIcon,
twitter: twitterIcon,
starIcon,
goldIcon,
difficultyStarIcon,
}),
};
},
computed: {
...mapState({
user: 'user.data',
partyMembers: 'party.members.data',
}),
questData () {
return quests.quests[this.group.quest.key];
},
members () {
return this.partyMembers.map(member => {
return {
name: member.profile.name,
accepted: this.group.quest.members[member._id],
};
});
},
},
methods: {
async questForceStart () {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/force-start'});
this.group.quest = quest;
this.close();
},
async questCancel () {
if (!confirm(this.$t('sureCancel'))) return;
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/cancel'});
this.group.quest = quest;
this.close();
},
close () {
this.$root.$emit('hide::modal', 'quest-details');
},
},
};
</script>

View File

@@ -1,5 +1,5 @@
<template lang="pug">
.standard-sidebar
.standard-sidebar.hidden-xs-down
.form-group
input.form-control.search(type="text", :placeholder="$t('search')", v-model='searchTerm')

View File

@@ -1,6 +1,6 @@
<template lang="pug">
.row
.clearfix.col-8.standard-page
.col-12.col-sm-8.clearfix.standard-page
.row
.col-6.title-details
h1(v-once) {{ $t('welcomeToTavern') }}
@@ -29,7 +29,7 @@
.hr.col-12
chat-message(:chat.sync='group.chat', :group-id='group._id', group-name='group.name')
.col-md-4.sidebar
.col-12.col-sm-4.sidebar
.section
.grassy-meadow-backdrop
.daniel_front
@@ -350,6 +350,7 @@
</style>
<script>
import debounce from 'lodash/debounce';
import { mapState } from 'client/libs/store';
import { TAVERN_ID } from '../../../common/script/constants';
@@ -527,7 +528,10 @@ export default {
};
document.body.removeChild(div);
},
updateCarretPosition (eventUpdate) {
updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
_updateCarretPosition (eventUpdate) {
if (eventUpdate.metaKey && eventUpdate.keyCode === 13) {
this.sendMessage();
return;

View File

@@ -1,6 +1,7 @@
<template lang="pug">
// @TODO: breakdown to componentes and use some SOLID
.row.stable(v-mousePosition="30", @mouseMoved="mouseMoved($event)")
.standard-sidebar
.standard-sidebar.col-3.hidden-xs-down
div
#npmMattStable.npc_matt
b-popover(
@@ -53,7 +54,7 @@
@change="updateHideMissing"
)
.standard-page
.standard-page.col-12.col-sm-9
.clearfix
h1.float-left.mb-0.page-header(v-once) {{ $t('stable') }}
@@ -73,55 +74,49 @@
span.badge.badge-pill.badge-default {{countOwnedAnimals(petGroups[0], 'pet')}}
div(
v-for="petGroup in petGroups",
v-for="(petGroup, index) in petGroups",
v-if="viewOptions[petGroup.key].selected",
:key="petGroup.key"
)
h4(v-if="viewOptions[petGroup.key].animalCount != 0") {{ petGroup.label }}
h4(v-if="viewOptions[petGroup.key].animalCount !== 0") {{ petGroup.label }}
itemRows(
:items="pets(petGroup, hideMissing, selectedSortBy, searchTextThrottled)",
:itemWidth=94,
:itemMargin=24,
:type="petGroup.key",
)
template(slot="item", scope="context")
div(
v-drag.drop.food="context.item.key",
@itemDragOver="onDragOver($event, context.item)",
@itemDropped="onDrop($event, context.item)",
@itemDragLeave="onDragLeave()",
:class="{'last': context.item.isLastInRow}"
.pet-row.d-flex(
v-for="(group, key, index) in pets(petGroup, hideMissing, selectedSortBy, searchTextThrottled)",
v-if='index === 0 || showMore === petGroup.key')
.pet-group(
v-for='item in group'
v-drag.drop.food="item.key",
@itemDragOver="onDragOver($event, item)",
@itemDropped="onDrop($event, item)",
@itemDragLeave="onDragLeave()",
:class="{'last': item.isLastInRow}"
)
petItem(
:item="item",
:itemContentClass="getPetItemClass(item)",
:popoverPosition="'top'",
:progress="item.progress",
:emptyItem="!item.isOwned()",
:showPopover="currentDraggingFood == null",
:highlightBorder="highlightPet == item.key",
@click="petClicked(item)"
)
petItem(
:item="context.item",
:itemContentClass="getPetItemClass(context.item)",
:popoverPosition="'top'",
:progress="context.item.progress",
:emptyItem="!context.item.isOwned()",
:showPopover="currentDraggingFood == null",
:highlightBorder="highlightPet == context.item.key",
@click="petClicked(context.item)"
)
span(slot="popoverContent")
div.hatchablePopover(v-if="context.item.isHatchable()")
h4.popover-content-title {{ context.item.name }}
div.popover-content-text(v-html="$t('haveHatchablePet', { potion: context.item.potionName, egg: context.item.eggName })")
div.potionEggGroup
div.potionEggBackground
div(:class="'Pet_HatchingPotion_'+context.item.potionKey")
div.potionEggBackground
div(:class="'Pet_Egg_'+context.item.eggKey")
span(slot="popoverContent")
div.hatchablePopover(v-if="item.isHatchable()")
h4.popover-content-title {{ item.name }}
div.popover-content-text(v-html="$t('haveHatchablePet', { potion: item.potionName, egg: item.eggName })")
div.potionEggGroup
div.potionEggBackground
div(:class="'Pet_HatchingPotion_'+item.potionKey")
div.potionEggBackground
div(:class="'Pet_Egg_'+item.eggKey")
div(v-else)
h4.popover-content-title {{ item.name }}
template(slot="itemBadge", scope="context")
starBadge(:selected="item.key === currentPet", :show="item.isOwned()", @click="selectPet(item)")
div(v-else)
h4.popover-content-title {{ context.item.name }}
template(slot="itemBadge", scope="context")
starBadge(
:selected="context.item.key === currentPet",
:show="context.item.isOwned()",
@click="selectPet(context.item)",
)
.btn.btn-flat.btn-show-more(@click="setShowMore(petGroup.key)", v-if='petGroup.key !== "specialPets"')
| {{ showMore === petGroup.key ? $t('showLess') : $t('showMore') }}
h2
| {{ $t('mounts') }}
@@ -135,31 +130,30 @@
)
h4(v-if="viewOptions[mountGroup.key].animalCount != 0") {{ mountGroup.label }}
itemRows(
:items="mounts(mountGroup, hideMissing, selectedSortBy, searchTextThrottled)",
:itemWidth=94,
:itemMargin=24,
:type="mountGroup.key",
)
template(slot="item", scope="context")
.pet-row.d-flex(v-for="(group, key, index) in mounts(mountGroup, hideMissing, selectedSortBy, searchTextThrottled)"
v-if='index === 0 || showMore === mountGroup.key')
.pet-group(v-for='item in group')
mountItem(
:item="context.item",
:itemContentClass="context.item.isOwned() ? ('Mount_Icon_' + context.item.key) : 'PixelPaw GreyedOut'",
:key="context.item.key",
:item="item",
:itemContentClass="item.isOwned() ? ('Mount_Icon_' + item.key) : 'PixelPaw GreyedOut'",
:key="item.key",
:popoverPosition="'top'",
:emptyItem="!context.item.isOwned()",
:emptyItem="!item.isOwned()",
:showPopover="true",
@click="selectMount(context.item)"
@click="selectMount(item)"
)
span(slot="popoverContent")
h4.popover-content-title {{ context.item.name }}
h4.popover-content-title {{ item.name }}
template(slot="itemBadge", scope="context")
starBadge(
:selected="context.item.key === currentMount",
:show="context.item.isOwned()",
@click="selectMount(context.item)",
:selected="item.key === currentMount",
:show="item.isOwned()",
@click="selectMount(item)",
)
.btn.btn-flat.btn-show-more(@click="setShowMore(mountGroup.key)", v-if='mountGroup.key !== "specialMounts"')
| {{ showMore === mountGroup.key ? $t('showLess') : $t('showMore') }}
drawer(
:title="$t('quickInventory')",
:errorMessage="(!hasDrawerTabItems(selectedDrawerTab)) ? ((selectedDrawerTab === 0) ? $t('noFoodAvailable') : $t('noSaddlesAvailable')) : null"
@@ -219,7 +213,6 @@
:visible="hatchablePet != null",
@change="resetHatchablePet($event)"
)
div.content(v-if="hatchablePet")
div.potionEggGroup
div.potionEggBackground
@@ -253,11 +246,9 @@
div.food-icon(:class="'Pet_Food_'+currentDraggingFood.key")
div.popover
div.popover-content {{ $t('clickOnPetToFeed', {foodName: currentDraggingFood.text() }) }}
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
@import '~client/assets/scss/modal.scss';
@@ -283,6 +274,20 @@
top: -16px !important;
}
.group {
height: 130px;
overflow: hidden;
}
.pet-row {
max-width: 100%;
flex-wrap: wrap;
.item {
margin-right: .5em;
}
}
.hatchablePopover {
width: 180px
}
@@ -500,6 +505,7 @@
import _filter from 'lodash/filter';
import _flatMap from 'lodash/flatMap';
import _throttle from 'lodash/throttle';
import groupBy from 'lodash/groupBy';
import Item from '../item';
import ItemRows from 'client/components/ui/itemRows';
@@ -585,6 +591,7 @@
currentDraggingFood: null,
selectedDrawerTab: 0,
showMore: '',
};
},
watch: {
@@ -712,7 +719,13 @@
},
},
methods: {
setShowMore (key) {
if (this.showMore === key) {
this.showMore = '';
return;
}
this.showMore = key;
},
getAnimalList (animalGroup, type) {
let key = animalGroup.key;
@@ -833,11 +846,39 @@
},
pets (animalGroup, hideMissing, sortBy, searchText) {
return this.listAnimals(animalGroup, 'pet', hideMissing, sortBy, searchText);
let pets = this.listAnimals(animalGroup, 'pet', hideMissing, sortBy, searchText);
// Don't group special
if (animalGroup.key === 'specialPets') {
return {none: pets};
}
let groupKey = 'eggKey';
if (sortBy === 'sortByColor') {
groupKey = 'potionKey';
} else if (sortBy === 'AZ') {
groupKey = '';
}
return groupBy(pets, groupKey);
},
mounts (animalGroup, hideMissing, sortBy, searchText) {
return this.listAnimals(animalGroup, 'mount', hideMissing, sortBy, searchText);
let mounts = this.listAnimals(animalGroup, 'mount', hideMissing, sortBy, searchText);
// Don't group special
if (animalGroup.key === 'specialMounts') {
return {none: mounts};
}
let groupKey = 'eggKey';
if (sortBy === 'sortByColor') {
groupKey = 'potionKey';
} else if (sortBy === 'AZ') {
groupKey = '';
}
return groupBy(mounts, groupKey);
},
getPetItemClass (pet) {

View File

@@ -37,11 +37,11 @@ div.item-with-icon.item-notifications.dropdown
@click='go("/user/profile")')
span.glyphicon.glyphicon-plus-sign
span {{ $t('haveUnallocated', {points: user.stats.points}) }}
a.dropdown-item(v-for='(message, key) in user.newMessages', v-if='message.value')
span(@click='navigateToGroup(key)')
a.dropdown-item(v-for='message in userNewMessages')
span(@click='navigateToGroup(message.key)')
span.glyphicon.glyphicon-comment
span {{message.name}}
span.clear-button(@click='clearMessages(key)', :popover="$t('clear')",
span.clear-button(@click='clearMessages(message.key)', :popover="$t('clear')",
popover-placement='right', popover-trigger='mouseenter', popover-append-to-body='true') Clear
a.dropdown-item(v-for='(notification, index) in user.groupNotifications', @click='viewGroupApprovalNotification(notification, index, true)')
span(:class="groupApprovalNotificationIcon(notification)")
@@ -156,6 +156,18 @@ export default {
return {name: ''};
// return this.user.party;
},
userNewMessages () {
// @TODO: For some reason data becomes corrupted. We should fix this on the server
let userNewMessages = [];
for (let key in this.user.newMessages) {
let message = this.user.newMessages[key];
if (message && message.name && message.value) {
message.key = key;
userNewMessages.push(message);
}
}
return userNewMessages;
},
},
methods: {
// @TODO: I hate this function, we can do better with a hashmap
@@ -244,8 +256,8 @@ export default {
count += this.user.stats.points > 0 ? 1 : 0;
}
if (this.user.newMessages) {
count += Object.keys(this.user.newMessages).length;
if (this.userNewMessages) {
count += Object.keys(this.userNewMessages).length;
}
return count;

View File

@@ -303,6 +303,10 @@ export default {
this.$root.$emit('show::modal', 'avatar-modal');
}
if (this.questCompleted) {
this.$root.$emit('show::modal', 'quest-completed');
}
// @TODO: This is a timeout to ensure dom is loaded
window.setTimeout(() => {
this.initTour();

View File

@@ -36,9 +36,9 @@
h6(v-once) {{ $t('class') + ': ' }}
// @TODO: what is classText
span(v-if='classText') {{ classText }}&nbsp;
button.btn.btn-danger.btn-xs(@click='changeClass(null)', v-once) {{ $t('changeClass') }}
small.cost 3
span.Pet_Currency_Gem1x.inline-gems
button.btn.btn-danger.btn-xs(@click='changeClassForUser(true)', v-once) {{ $t('changeClass') }}
small.cost &nbsp; 3 {{ $t('gems') }}
// @TODO add icon span.Pet_Currency_Gem1x.inline-gems
hr
div
@@ -82,7 +82,7 @@
button.btn.btn-primary(@click='showBailey()', popover-trigger='mouseenter', popover-placement='right', :popover="$t('showBaileyPop')") {{ $t('showBailey') }}
button.btn.btn-primary(@click='openRestoreModal()', popover-trigger='mouseenter', popover-placement='right', :popover="$t('fixValPop')") {{ $t('fixVal') }}
button.btn.btn-primary(v-if='user.preferences.disableClasses == true', @click='changeClass({})',
button.btn.btn-primary(v-if='user.preferences.disableClasses == true', @click='changeClassForUser(false)',
popover-trigger='mouseenter', popover-placement='right', :popover="$t('enableClassPop')") {{ $t('enableClass') }}
hr
@@ -378,8 +378,8 @@ export default {
this.$router.go('/tasks');
},
async changeClass () {
if (!confirm('Are you sure you want to change your class for 3 gems?')) return;
async changeClassForUser (confirmationNeeded) {
if (confirmationNeeded && !confirm(this.$t('changeClassConfirmCost'))) return;
try {
changeClass(this.user);
await axios.post('/api/v3/user/change-class');

View File

@@ -0,0 +1,90 @@
<template lang='pug'>
div
.media
.align-self-center.right-margin(:class='baileyClass')
.media-body
h1.align-self-center(v-markdown='$t("newStuff")')
h2 9/28/2017 - HABITICA'S WEBSITE LEVELS UP!
hr
p Welcome to the new Habitica! We're so excited to share it with you at last. This project, which has been a labor of love since last December, is the single biggest update that Habitica has ever released (with over 150 pages of designs, an entire rewrite of all of our front-end code, countless rounds of testing and iteration, and many, many meetings). Just refresh your page to load the new website!
.promo_login_screen.center-block
p(v-markdown="'You can find a full list of changes [here](http://habitica.wikia.com/wiki/Habitica_Redesign_Fact_Sheet), as well as explanations for why we made each, but here are a few quick tips to help you get oriented:'")
.grassy-meadow-backdrop
.daniel_front
ul
li There's a ton of new art around the site! Peek at the NPCs and Guild chats to admire some of the changes.
li Click directly on your tasks to bring up the edit modal!
li The navigation bar contains several changes to be more intuitive for new users, so we recommend taking some time to open the drop-down menus and familiarize yourself with the new locations. Notably, the User menu has moved to an icon in the upper-right corner.
li You can now pin any purchasable item in the game to your Rewards. You can pin Backgrounds, too! Just hover over the shop icon and click the pin. When you head back to the tasks page, you'll see it in your Rewards column!
li There are lots of new filtering options, especially for Guilds and Challenges!
li There are visual upgrades for every aspect of the site, from the front page to the Seasonal Shop. We hope that you like them!
li Some of these upgrades have made their way to our <a href='https://itunes.apple.com/us/app/habitica/id994882113?ls=1&mt=8' target='_blank'>iOS</a> and <a href='https://play.google.com/store/apps/details?id=com.habitrpg.android.habitica' target='_blank'>Android</a> apps! Be sure to download the latest updates for the best performance.
.seasonal-shop-backdrop
.sorceress_front
p Have general questions about how the new site works? Come ask in the <a href='https://habitica.com/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a'>Habitica Help Guild</a>, and we'll be glad to assist! Likewise, if you encounter a persistent bug that isn't fixed by refreshing your page, you can report it in the <a href='https://habitica.com/groups/guild/a29da26b-37de-4a71-b0c6-48e72a900dac'>Report a Bug Guild</a> and we will investigate as soon as possible.
p If you have thoughts about the new design, we look forward to hearing them. On <strong>October 12th</strong> we will be opening a Trello card for public comments on the redesign. This delay will give us time to focus our attention on answering general questions and fixing any bugs that might arise. For this reason, we ask that you hold back on sharing your feedback about the new designs until that Trello card is live and linked in a Bailey announcement. Thanks for understanding!
.promo_veteran_pets_2017.center-block
p This is a major time of change for Habitica, so to thank you for your patience, we've given everyone a Veteran Pet! Newer users have received a Veteran Wolf, and older users have received (depending on which pets they already had) a Veteran Tiger, a Veteran Lion, or a Veteran Bear. Head to the new <a href='https://habitica.com/inventory/stable'>Stable</a> page and filter to the Special Pets section to see the latest addition to your menagerie!"')
p We are so excited to continue to build Habitica with you. Now go check it out!
p Thank you for playing, and good luck with your tasks <3
.small by Apollo, piyorii, TheHollidayInn, Paglias, Negue, Sabe, Alys, viirus, Lemoness, redphoenix, beffymaroo, and all our awesome testers!
br
</template>
<style lang='scss' scoped>
@import '~client/assets/scss/static.scss';
.center-block {
margin: 0 auto;
}
.grassy-meadow-backdrop {
background-image: url('~assets/images/npc/fall/tavern_background.png');
background-repeat: repeat-x;
width: 100%;
height: 246px;
}
.daniel_front {
background-image: url('~assets/images/npc/fall/tavern_npc.png');
height: 246px;
width: 471px;
background-repeat: no-repeat;
margin: 0 auto;
}
.seasonal-shop-backdrop {
background: url('~assets/images/npc/fall/seasonal_shop_opened_background.png');
background-repeat: repeat-x;
}
.sorceress_front {
background-image: url('~assets/images/npc/fall/seasonal_shop_opened_npc.png');
height: 246px;
width: 471px;
background-repeat: no-repeat;
margin: 0 auto;
}
</style>
<script>
import markdown from 'client/directives/markdown';
export default {
data () {
let worldDmg = {
bailey: false,
};
return {
baileyClass: {
'npc_bailey_broken': worldDmg.bailey, // eslint-disable-line
'npc_bailey': !worldDmg.bailey, // eslint-disable-line
},
};
},
directives: {
markdown,
},
};
</script>

View File

@@ -1,14 +1,14 @@
<template lang='pug'>
div
static-header(:class='{"home-header": $route.name === "home"}')
static-header(v-if='showContentWrap', :class='{"home-header": $route.name === "home"}')
.static-wrapper
router-view
#purple-footer
#purple-footer(v-if='showContentWrap')
app-footer
#bottom-wrap.purple-4
#bottom-wrap.purple-4(v-if='showContentWrap')
#bottom-background
.seamless_mountains_demo_repeat
.midground_foreground_extended2
@@ -149,5 +149,10 @@
AppFooter,
StaticHeader,
},
computed: {
showContentWrap () {
return this.$route.name !== 'news';
},
},
};
</script>

View File

@@ -43,7 +43,7 @@
@import '~client/assets/scss/colors.scss';
.tasks-column {
height: 556px;
min-height: 556px;
}
.task-wrapper + .reward-items {
@@ -60,11 +60,9 @@
border-radius: 4px;
background: $gray-600;
padding: 8px;
// not sure why but this is necessary or the last task will overflow the container
padding-bottom: 0.1px;
position: relative;
height: calc(100% - 64px);
overflow: auto;
position: relative; // needed for the .bottom-gradient to be position: absolute
height: calc(100% - 56px);
padding-bottom: 30px;
}
.bottom-gradient {
@@ -161,6 +159,7 @@
<script>
import Task from './task';
import sortBy from 'lodash/sortBy';
import { mapState, mapActions } from 'client/libs/store';
import { shouldDo } from 'common/script/cron';
import inAppRewards from 'common/script/libs/inAppRewards';
@@ -205,7 +204,7 @@ export default {
label: 'todos',
filters: [
{label: 'remaining', filter: t => !t.completed, default: true}, // active
{label: 'scheduled', filter: t => !t.completed && t.date},
{label: 'scheduled', filter: t => !t.completed && t.date, sort: t => t.date},
{label: 'complete2', filter: t => t.completed},
],
},
@@ -318,6 +317,10 @@ export default {
this.loadCompletedTodos();
}
this.activeFilters[type] = filter;
if (filter.sort) {
this.tasks[`${type}s`] = sortBy(this.tasks[`${type}s`], filter.sort);
}
},
setColumnBackgroundVisibility () {
this.$nextTick(() => {

View File

@@ -72,7 +72,7 @@
span.text {{$t(type)}}
.row.tasks-columns
task-column.col-3(
task-column.col-lg-3.col-md-6(
v-for="column in columns",
:type="column", :key="column",
:isUser="true", :searchText="searchTextThrottled",
@@ -114,25 +114,25 @@
}
.dropdown-icon-item {
.icon_habit {
width: 30px;
height: 20px;
}
.icon_habit {
width: 30px;
height: 20px;
}
.icon_daily {
width: 24px;
height: 20px;
}
.icon_daily {
width: 24px;
height: 20px;
}
.icon_todo {
width: 20px;
height: 20px;
}
.icon_todo {
width: 20px;
height: 20px;
}
.icon_reward {
width: 26px;
height: 20px;
}
.icon_reward {
width: 26px;
height: 20px;
}
}
.dropdown-icon-item:hover .svg-icon, .dropdown-item.active .svg-icon {

View File

@@ -5,7 +5,7 @@ div
.profile-actions
button.btn.btn-secondary(@click='sendMessage()')
.svg-icon.message-icon(v-html="icons.message")
button.btn.btn-secondary(v-if='userLoggedIn.inbox.blocks.indexOf(user._id) === -1', :tooltip="$t('unblock')",
button.btn.btn-secondary(v-if='user._id !== this.userLoggedIn._id && userLoggedIn.inbox.blocks.indexOf(user._id) === -1', :tooltip="$t('unblock')",
@click="blockUser()", tooltip-placement='right')
span.glyphicon.glyphicon-plus
| {{$t('block')}}
@@ -795,7 +795,7 @@ export default {
return display;
},
allocate (stat) {
allocate(this.user, stat);
allocate(this.user, {query: { stat }});
axios.post(`/api/v3/user/allocate?stat=${stat}`);
},
allocateNow () {

View File

@@ -19,6 +19,7 @@ const FeaturesPage = () => import(/* webpackChunkName: "static" */'./components/
const HomePage = () => import(/* webpackChunkName: "static" */'./components/static/home');
const GroupPlansPage = () => import(/* webpackChunkName: "static" */'./components/static/groupPlans');
const MerchPage = () => import(/* webpackChunkName: "static" */'./components/static/merch');
const NewsPage = () => import(/* webpackChunkName: "static" */'./components/static/newStuff');
const OverviewPage = () => import(/* webpackChunkName: "static" */'./components/static/overview');
const PressKitPage = () => import(/* webpackChunkName: "static" */'./components/static/pressKit');
const PrivacyPage = () => import(/* webpackChunkName: "static" */'./components/static/privacy');
@@ -250,6 +251,7 @@ const router = new VueRouter({
{ name: 'groupPlans', path: 'group-plans', component: GroupPlansPage, meta: {requiresLogin: false}},
{ name: 'home', path: 'home', component: HomePage, meta: {requiresLogin: false} },
{ name: 'merch', path: 'merch', component: MerchPage, meta: {requiresLogin: false}},
{ name: 'news', path: 'new-stuff', component: NewsPage, meta: {requiresLogin: false}},
{ name: 'overview', path: 'overview', component: OverviewPage, meta: {requiresLogin: false}},
{ name: 'plans', path: 'plans', component: GroupPlansPage, meta: {requiresLogin: false}},
{ name: 'pressKit', path: 'press-kit', component: PressKitPage, meta: {requiresLogin: false}},

View File

@@ -125,6 +125,7 @@
"mystery": "Mystery",
"changeClass": "Change Class, Refund Attribute Points",
"lvl10ChangeClass": "To change class you must be at least level 10.",
"changeClassConfirmCost": "Are you sure you want to change your class for 3 Gems?",
"invalidClass":"Invalid class. Please specify 'warrior', 'rogue', 'wizard', or 'healer'.",
"levelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options.",
"unallocated": "Unallocated Attribute Points",
@@ -158,7 +159,7 @@
"respawn": "Respawn!",
"youDied": "You Died!",
"dieText": "You've lost a Level, all your Gold, and a random piece of Equipment. Arise, Habiteer, and try again! Curb those negative Habits, be vigilant in completion of Dailies, and hold death at arm's length with a Health Potion if you falter!",
"sureReset": "Are you sure? This will reset your character's class and allocated points (you'll get them all back to re-allocate), and costs 3 gems.",
"sureReset": "Are you sure? This will reset your character's class and allocated points (you'll get them all back to re-allocate), and costs 3 Gems.",
"purchaseFor": "Purchase for <%= cost %> Gems?",
"notEnoughMana": "Not enough mana.",
"invalidTarget": "You can't cast a skill on that.",

View File

@@ -388,5 +388,7 @@
"selectPartyMember": "Select a Party Member",
"areYouSureDeleteMessage": "Are you sure you want to delete this message?",
"reverseChat": "Reverse Chat",
"invites": "Invites"
"invites": "Invites",
"details": "Details",
"participantDesc": "Once all members have either accepted or declined, the Quest begins. Only those that clicked 'accept' will be able to participate in the Quest and receive the drops."
}

View File

@@ -35,7 +35,7 @@ api.verifyGemPurchase = async function verifyGemPurchase (user, receipt, signatu
let isValidated = iap.isValidated(googleRes);
if (!isValidated) throw new NotAuthorized(this.constants.RESPONSE_INVALID_RECEIPT);
let receiptObj = JSON.parse(testObj.data); // passed as a string
let receiptObj = typeof testObj.data === 'string' ? JSON.parse(testObj.data) : testObj.data; // passed as a string
let token = receiptObj.token || receiptObj.purchaseToken;
let existingReceipt = await IapPurchaseReceipt.findOne({
@@ -106,7 +106,7 @@ api.subscribe = async function subscribe (sku, user, receipt, signature, headers
signature,
};
let receiptObj = JSON.parse(receipt); // passed as a string
let receiptObj = typeof receipt === 'string' ? JSON.parse(receipt) : receipt; // passed as a string
let token = receiptObj.token || receiptObj.purchaseToken;
let existingUser = await User.findOne({

View File

@@ -11,9 +11,19 @@ const BUILD_DIR = path.join(__dirname, '/../../build');
module.exports = function staticMiddleware (expressApp) {
// Expose static files for new client
// if (IS_PROD && IS_NEW_CLIENT_ENABLED) {
expressApp.use('/static/js', express.static(`${PUBLIC_DIR}/../../dist-client/static/js`, { maxAge: MAX_AGE }));
expressApp.use('/static/css', express.static(`${PUBLIC_DIR}/../../dist-client/static/css`, { maxAge: MAX_AGE }));
expressApp.use('/static/img', express.static(`${PUBLIC_DIR}/../../dist-client/static/img`, { maxAge: MAX_AGE }));
// @TODO img/js/css under /static have their names hashed after every change so they can be cached
// Not files in /audio and /sprites, that's why we don't cache them.
// Hash their file names and cache the entire /static folder
expressApp.use('/static', express.static(`${PUBLIC_DIR}/../../dist-client/static`));
// }
// @TODO all these paths are not used by the new client, remove them
// But first check that they're not used anywhere else
// In particular the images used by emails
// TODO move all static files to a single location (one for public and one for build)
expressApp.use(express.static(BUILD_DIR, { maxAge: MAX_AGE }));