Merge develop into release (#9154)
* Client: fix Apidoc and move email files (#9139) * fix apidoc * move emails files * quest leader can start/end quest; admins can edit challenges/guilds; reverse chat works; remove static/videos link; etc (#9140) * enable link to markdown info on group and challenge edit screen * allow admin (moderators and staff) to edit challenges * allow admin (moderators and staff) to edit guilds Also add some unrelated TODO comments. * allow any party member (not just leader) to start quest from party page * allow quest owner to cancel, begin, abort quest Previously only the party leader could see those buttons. The leader still can. This also hides those buttons from all other party members. * enable reverse chat in guilds and party * remove outdated videos from press kit * adjust various wordings * Be consistent with capitalization of Check-In. (#9118) * limit for inlined svg images and make home leaner by not bundling it with the rest of static pages * sep 27 fixes (#9088) * fix item paddings / drawer width * expand the width of item-rows by the margin of an item * fix hatchedPet-dialog * fix hatching-modal * remove min-height * Oct 3 fixes (#9148) * Only show level after yesterdailies modal * Fixed zindex * Added spcial spells to rewards column * Added single click buy for health and armoire * Prevented task scoring when casting a spell * Renamed generic purchase method * Updated nav for small screen * Hide checklist while casting * fix some text describing menu items (#9145)
@@ -47,10 +47,5 @@ gulp.task('build:prod', [
|
||||
'build:server',
|
||||
'prepare:staticNewStuff',
|
||||
'build:client',
|
||||
], (done) => {
|
||||
runSequence(
|
||||
'grunt-build:prod',
|
||||
'apidoc',
|
||||
done
|
||||
);
|
||||
});
|
||||
]);
|
||||
|
||||
@@ -105,7 +105,12 @@ const baseConfig = {
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: [
|
||||
{ loader: 'svg-url-loader' },
|
||||
{
|
||||
loader: 'svg-url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
},
|
||||
},
|
||||
{ loader: 'svgo-loader' },
|
||||
],
|
||||
include: [path.resolve(projectRoot, 'website/client/assets/svg/for-css')],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
.items > div {
|
||||
display: inline-block;
|
||||
margin-right: 23px;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.items > div:last-of-type {
|
||||
|
||||
@@ -3,9 +3,10 @@ div
|
||||
inbox-modal
|
||||
creator-intro
|
||||
profile
|
||||
nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-lg
|
||||
nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-md
|
||||
.navbar-header
|
||||
.logo.svg-icon(v-html="icons.logo")
|
||||
.logo.svg-icon.hidden-lg-down(v-html="icons.logo")
|
||||
.svg-icon.gryphon.hidden-xl-up
|
||||
b-collapse#nav_collapse.collapse.navbar-collapse(is-nav)
|
||||
ul.navbar-nav.mr-auto
|
||||
router-link.nav-item(tag="li", :to="{name: 'tasks'}", exact)
|
||||
@@ -93,58 +94,34 @@ div
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
@import '~client/assets/scss/utils.scss';
|
||||
|
||||
/* Less than Desktops and laptops ----------- */
|
||||
@media only screen and (max-width : 1224px) {
|
||||
#nav_collapse {
|
||||
background: $purple-100;
|
||||
margin-top: 1em;
|
||||
margin-left: 70%;
|
||||
padding-bottom: 1em;
|
||||
|
||||
a {
|
||||
padding: .5em !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute !important;
|
||||
left: -10em;
|
||||
top: -.5em;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width : 1224px) and (min-width: 1200px) {
|
||||
#nav_collapse {
|
||||
margin-top: 37em !important;
|
||||
|
||||
a {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-collapse.collapse {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.navbar-collapse.collapse.show {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.navbar-toggler, .navbar-nav {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navbar-toggleable-lg .navbar-collapse {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1280px) {
|
||||
.nav-link {
|
||||
padding: .8em 1em !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.navbar-header {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.gryphon {
|
||||
background-image: url('~assets/images/melior@3x.png');
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-size: cover;
|
||||
color: $white;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 990px) {
|
||||
#nav_collapse {
|
||||
margin-top: 1.3em;
|
||||
background-color: $purple-200;
|
||||
}
|
||||
}
|
||||
|
||||
nav.navbar {
|
||||
background: $purple-100 url(~assets/svg/for-css/bits.svg) right no-repeat;
|
||||
padding-left: 25px;
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
b-dropdown.create-dropdown(text="Select a Participant")
|
||||
b-dropdown-item(v-for="member in members", :key="member._id", @click="openMemberProgressModal(member._id)")
|
||||
| {{ member.profile.name }}
|
||||
span(v-if='isLeader')
|
||||
span(v-if='isLeader || isAdmin')
|
||||
b-dropdown.create-dropdown(:text="$t('addTaskToChallenge')", :variant="'success'")
|
||||
b-dropdown-item(v-for="type in columns", :key="type", @click="createTask(type)")
|
||||
| {{$t(type)}}
|
||||
@@ -64,13 +64,13 @@
|
||||
button.btn.btn-success(v-once, @click='joinChallenge()') {{$t('joinChallenge')}}
|
||||
div(v-if='isMember')
|
||||
button.btn.btn-danger(v-once, @click='leaveChallenge()') {{$t('leaveChallenge')}}
|
||||
div(v-if='isLeader')
|
||||
div(v-if='isLeader || isAdmin')
|
||||
button.btn.btn-secondary(v-once, @click='edit()') {{$t('editChallenge')}}
|
||||
div(v-if='isLeader')
|
||||
div(v-if='isLeader || isAdmin')
|
||||
button.btn.btn-danger(v-once, @click='closeChallenge()') {{$t('endChallenge')}}
|
||||
div(v-if='isLeader || isAdmin')
|
||||
button.btn.btn-secondary(v-once, @click='exportChallengeCsv()') {{$t('exportChallengeCsv')}}
|
||||
div(v-if='isLeader')
|
||||
div(v-if='isLeader || isAdmin')
|
||||
button.btn.btn-secondary(v-once, @click='cloneChallenge()') {{$t('clone')}}
|
||||
.description-section
|
||||
h2 {{$t('challengeSummary')}}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
.form-group
|
||||
label
|
||||
strong(v-once) {{$t('challengeDescription')}} *
|
||||
a.float-right {{ $t('markdownFormattingHelp') }}
|
||||
a.float-right(v-markdown='$t("markdownFormattingHelp")')
|
||||
textarea.description-textarea.form-control(:placeholder="$t('challengeDescriptionPlaceholder')", v-model="workingChallenge.description")
|
||||
.form-group(v-if='creating')
|
||||
label
|
||||
@@ -136,6 +136,8 @@ import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||
import bFormInput from 'bootstrap-vue/lib/components/form-input';
|
||||
|
||||
import markdownDirective from 'client/directives/markdown';
|
||||
|
||||
import { TAVERN_ID, MIN_SHORTNAME_SIZE_FOR_CHALLENGES, MAX_SUMMARY_SIZE_FOR_CHALLENGES } from '../../../common/script/constants';
|
||||
import { mapState } from 'client/libs/store';
|
||||
|
||||
@@ -147,6 +149,9 @@ export default {
|
||||
bDropdownItem,
|
||||
bFormInput,
|
||||
},
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
data () {
|
||||
let categoryOptions = [
|
||||
{
|
||||
|
||||
@@ -56,15 +56,16 @@
|
||||
button.btn.btn-success(class='btn-success', v-if='isLeader && !group.purchased.active', @click='upgradeGroup()')
|
||||
| {{ $t('upgrade') }}
|
||||
.button-container
|
||||
button.btn.btn-primary(b-btn, @click="updateGuild", v-once, v-if='isLeader') {{ $t('edit') }}
|
||||
button.btn.btn-primary(b-btn, @click="updateGuild", v-once, v-if='isLeader || isAdmin') {{ $t('edit') }}
|
||||
.button-container
|
||||
button.btn.btn-success(class='btn-success', v-if='!isMember', @click='join()') {{ $t('join') }}
|
||||
.button-container
|
||||
button.btn.btn-primary(v-once, @click='showInviteModal()') {{$t('invite')}}
|
||||
// @TODO: hide the invitation button if there's an active group plan and the player is not the leader
|
||||
.button-container
|
||||
// @TODO: V2 button.btn.btn-primary(v-once, v-if='!isLeader') {{$t('messageGuildLeader')}}
|
||||
// @TODO: V2 button.btn.btn-primary(v-once, v-if='!isLeader') {{$t('messageGuildLeader')}} // Suggest making the button visible to the leader too - useful for them to test how the feature works or to send a note to themself. -- Alys
|
||||
.button-container
|
||||
// @TODO: V2 button.btn.btn-primary(v-once, v-if='isMember && !isParty') {{$t('donateGems')}}
|
||||
// @TODO: V2 button.btn.btn-primary(v-once, v-if='isMember && !isParty') {{$t('donateGems')}} // Suggest removing the isMember restriction - it's okay if non-members donate to a public guild. Also probably allow it for parties if parties can buy imagery. -- Alys
|
||||
|
||||
.section-header(v-if='isParty')
|
||||
.row
|
||||
@@ -81,7 +82,7 @@
|
||||
.svg-icon(v-html="icons.questIcon")
|
||||
h4(v-once) {{ $t('youAreNotOnQuest') }}
|
||||
p(v-once) {{ $t('questDescription') }}
|
||||
button.btn.btn-secondary(v-once, @click="openStartQuestModal()", v-if='isLeader') {{ $t('startAQuest') }}
|
||||
button.btn.btn-secondary(v-once, @click="openStartQuestModal()") {{ $t('startAQuest') }}
|
||||
.row.quest-active-section(v-if='isParty && onPendingQuest && !onActiveQuest')
|
||||
.col-2
|
||||
.quest(:class='`inventory_quest_scroll_${questData.key}`')
|
||||
@@ -129,7 +130,7 @@
|
||||
.col-6
|
||||
span.float-left
|
||||
| Rage {{questData.boss.rage.value}}
|
||||
button.btn.btn-secondary(v-once, @click="questAbort()", v-if='isLeader') {{ $t('abort') }}
|
||||
button.btn.btn-secondary(v-once, @click="questAbort()", v-if='canEditQuest') {{ $t('abort') }}
|
||||
|
||||
.section-header(v-if='!isParty')
|
||||
.row
|
||||
@@ -562,12 +563,17 @@ export default {
|
||||
isLeader () {
|
||||
return this.user._id === this.group.leader._id;
|
||||
},
|
||||
isAdmin () {
|
||||
return Boolean(this.user.contributor.admin);
|
||||
},
|
||||
isMember () {
|
||||
return this.isMemberOfGroup(this.user, this.group);
|
||||
},
|
||||
canEditQuest () {
|
||||
let isQuestLeader = this.group.quest && this.group.quest.leader === this.user._id;
|
||||
return isQuestLeader;
|
||||
if (!this.group.quest) return false;
|
||||
let isQuestLeader = this.group.quest.leader === this.user._id;
|
||||
let isPartyLeader = this.group.leader._id === this.user._id;
|
||||
return isQuestLeader || isPartyLeader;
|
||||
},
|
||||
isMemberOfPendingQuest () {
|
||||
let userid = this.user._id;
|
||||
@@ -710,6 +716,9 @@ export default {
|
||||
fetchRecentMessages () {
|
||||
this.fetchGuild();
|
||||
},
|
||||
reverseChat () {
|
||||
this.group.chat.reverse();
|
||||
},
|
||||
updateGuild () {
|
||||
this.$store.state.editingGroup = this.group;
|
||||
this.$root.$emit('show::modal', 'guild-form');
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
.form-group
|
||||
label
|
||||
strong(v-once) {{$t('groupDescription')}} *
|
||||
a.float-right {{ $t('markdownFormattingHelp') }}
|
||||
a.float-right(v-markdown='$t("markdownFormattingHelp")')
|
||||
textarea.form-control.description-textarea(type="text", textarea, :placeholder="isParty ? $t('partyDescriptionPlaceholder') : $t('guildDescriptionPlaceholder')", v-model="workingGroup.description")
|
||||
|
||||
.form-group(v-if='creatingParty && !workingGroup.id')
|
||||
@@ -177,6 +177,7 @@ import bTooltip from 'bootstrap-vue/lib/components/tooltip';
|
||||
|
||||
import { mapState } from 'client/libs/store';
|
||||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
import markdownDirective from 'client/directives/markdown';
|
||||
import gemIcon from 'assets/svg/gem.svg';
|
||||
import informationIcon from 'assets/svg/information.svg';
|
||||
|
||||
@@ -198,6 +199,9 @@ export default {
|
||||
bTooltip,
|
||||
toggleSwitch,
|
||||
},
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
data () {
|
||||
let data = {
|
||||
workingGroup: {
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
.pending.float-right(v-if='member.accepted === null') {{ $t('pending') }}
|
||||
div(v-if='questData')
|
||||
questDialogContent(:item="questData")
|
||||
div.text-center.actions
|
||||
div.text-center.actions(v-if='canEditQuest')
|
||||
div
|
||||
button.btn.btn-secondary(v-once, @click="questForceStart()") {{ $t('begin') }}
|
||||
// @TODO don't allow the party leader to start the quest until the leader has accepted or rejected the invitation (users get confused and think "begin" means "join quest")
|
||||
div
|
||||
.cancel(v-once, @click="questCancel()") {{ $t('cancel') }}
|
||||
.side-panel(v-if='questData')
|
||||
@@ -189,6 +190,12 @@ export default {
|
||||
};
|
||||
});
|
||||
},
|
||||
canEditQuest () {
|
||||
if (!this.group.quest) return false;
|
||||
let isQuestLeader = this.group.quest.leader === this.user._id;
|
||||
let isPartyLeader = this.group.leader._id === this.user._id;
|
||||
return isQuestLeader || isPartyLeader;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async questForceStart () {
|
||||
|
||||
@@ -127,10 +127,7 @@
|
||||
:count="context.item.quantity"
|
||||
)
|
||||
|
||||
hatchedPetDialog(
|
||||
:pet="hatchedPet",
|
||||
@closed="closeHatchedPetDialog()"
|
||||
)
|
||||
hatchedPetDialog()
|
||||
|
||||
div.hatchingPotionInfo(ref="draggingPotionInfo")
|
||||
div(v-if="currentDraggingPotion != null")
|
||||
@@ -251,7 +248,6 @@ export default {
|
||||
|
||||
currentDraggingPotion: null,
|
||||
potionClickMode: false,
|
||||
hatchedPet: null,
|
||||
cardOptions: {
|
||||
cardType: '',
|
||||
messageOptions: 0,
|
||||
@@ -345,7 +341,7 @@ export default {
|
||||
},
|
||||
hatchPet (potion, egg) {
|
||||
this.$store.dispatch('common:hatch', {egg: egg.key, hatchingPotion: potion.key});
|
||||
this.hatchedPet = createAnimal(egg, potion, 'pet', this.content, this.user.items);
|
||||
this.$root.$emit('hatchedPet::open', createAnimal(egg, potion, 'pet', this.content, this.user.items));
|
||||
},
|
||||
onDragEnd () {
|
||||
this.currentDraggingPotion = null;
|
||||
@@ -405,9 +401,6 @@ export default {
|
||||
this.potionClickMode = false;
|
||||
}
|
||||
},
|
||||
closeHatchedPetDialog () {
|
||||
this.hatchedPet = null;
|
||||
},
|
||||
|
||||
async itemClicked (groupKey, item) {
|
||||
if (item.type && item.type === 'card') {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<template lang="pug">
|
||||
|
||||
b-modal#hatchedPet-modal(
|
||||
:visible="true",
|
||||
v-if="pet != null",
|
||||
:hide-header="true"
|
||||
)
|
||||
div.content
|
||||
div.dialog-header.title You hatched a new pet!
|
||||
div.content(v-if="pet != null")
|
||||
div.dialog-header.title(v-once) {{ $t('hatchedPetGeneric') }}
|
||||
|
||||
|
||||
div.inner-content
|
||||
@@ -45,7 +43,7 @@
|
||||
}
|
||||
|
||||
.inner-content {
|
||||
margin: 33px auto auto;
|
||||
margin: 24px auto auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -77,16 +75,33 @@
|
||||
components: {
|
||||
bModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
pet: null,
|
||||
};
|
||||
},
|
||||
created () {
|
||||
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('hatchedPet::open', this.openDialog);
|
||||
},
|
||||
destroyed () {
|
||||
this.$root.$off('hatchedPet::open', this.openDialog);
|
||||
},
|
||||
methods: {
|
||||
openDialog (item) {
|
||||
this.pet = item;
|
||||
this.$root.$emit('show::modal', 'hatchedPet-modal');
|
||||
},
|
||||
|
||||
close () {
|
||||
this.$emit('closed', this.item);
|
||||
this.$root.$emit('hide::modal', 'hatchedPet-modal');
|
||||
this.pet = null;
|
||||
},
|
||||
},
|
||||
props: {
|
||||
pet: {
|
||||
type: Object,
|
||||
},
|
||||
hideText: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
@@ -210,7 +210,6 @@
|
||||
div.content-text(v-once) {{ $t('welcomeStableText') }}
|
||||
|
||||
b-modal#hatching-modal(
|
||||
:visible="hatchablePet != null",
|
||||
@change="resetHatchablePet($event)"
|
||||
)
|
||||
div.content(v-if="hatchablePet")
|
||||
@@ -230,9 +229,7 @@
|
||||
button.btn.btn-secondary.btn-flat(@click="closeHatchPetDialog()") {{ $t('cancel') }}
|
||||
|
||||
hatchedPetDialog(
|
||||
:pet="hatchedPet",
|
||||
:hideText="true",
|
||||
@closed="closeHatchedPetDialog()"
|
||||
)
|
||||
|
||||
div.foodInfo(ref="dragginFoodInfo")
|
||||
@@ -337,11 +334,6 @@
|
||||
padding-right:0;
|
||||
}
|
||||
|
||||
.drawer-container {
|
||||
// 3% padding + 252px sidebar width
|
||||
left: calc(3% + 252px) !important;
|
||||
}
|
||||
|
||||
.svg-icon.inline.icon-16 {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
@@ -392,7 +384,6 @@
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 24px;
|
||||
margin-top: 24px;
|
||||
font-family: Roboto;
|
||||
font-size: 20px;
|
||||
@@ -588,7 +579,6 @@
|
||||
highlightPet: '',
|
||||
|
||||
hatchablePet: null,
|
||||
hatchedPet: null,
|
||||
foodClickMode: false,
|
||||
currentDraggingFood: null,
|
||||
|
||||
@@ -918,7 +908,8 @@
|
||||
|
||||
hatchPet (pet) {
|
||||
this.$store.dispatch('common:hatch', {egg: pet.eggKey, hatchingPotion: pet.potionKey});
|
||||
this.hatchedPet = pet;
|
||||
|
||||
this.$root.$emit('hatchedPet::open', pet);
|
||||
this.closeHatchPetDialog();
|
||||
},
|
||||
|
||||
@@ -974,6 +965,8 @@
|
||||
}
|
||||
// opens the hatch dialog
|
||||
this.hatchablePet = pet;
|
||||
|
||||
this.$root.$emit('show::modal', 'hatching-modal');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -988,9 +981,6 @@
|
||||
closeHatchPetDialog () {
|
||||
this.$root.$emit('hide::modal', 'hatching-modal');
|
||||
},
|
||||
closeHatchedPetDialog () {
|
||||
this.hatchedPet = null;
|
||||
},
|
||||
|
||||
resetHatchablePet ($event) {
|
||||
if (!$event) {
|
||||
|
||||
@@ -158,6 +158,7 @@ export default {
|
||||
|
||||
return {
|
||||
yesterDailies: [],
|
||||
levelBeforeYesterdailies: 0,
|
||||
notificationData: {},
|
||||
unlockLevels,
|
||||
lastShownNotifications,
|
||||
@@ -256,12 +257,8 @@ export default {
|
||||
this.mp(mana);
|
||||
},
|
||||
userLvl (after, before) {
|
||||
if (after <= before) return;
|
||||
this.lvl();
|
||||
this.playSound('Level_Up');
|
||||
if (this.user._tmp && this.user._tmp.drop && this.user._tmp.drop.type === 'Quest') return;
|
||||
if (this.unlockLevels[`${after}`]) return;
|
||||
if (!this.user.preferences.suppressModals.levelUp) this.$root.$emit('show::modal', 'level-up');
|
||||
if (after <= before || this.isRunningYesterdailies) return;
|
||||
this.showLevelUpNotifications(after);
|
||||
},
|
||||
userClassSelect (after) {
|
||||
if (!after) return;
|
||||
@@ -334,6 +331,13 @@ export default {
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
showLevelUpNotifications (newlevel) {
|
||||
this.lvl();
|
||||
this.playSound('Level_Up');
|
||||
if (this.user._tmp && this.user._tmp.drop && this.user._tmp.drop.type === 'Quest') return;
|
||||
if (this.unlockLevels[`${newlevel}`]) return;
|
||||
if (!this.user.preferences.suppressModals.levelUp) this.$root.$emit('show::modal', 'level-up');
|
||||
},
|
||||
playSound (sound) {
|
||||
this.$root.$emit('playSound', sound);
|
||||
},
|
||||
@@ -390,6 +394,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
this.levelBeforeYesterdailies = this.user.stats.lvl;
|
||||
this.$root.$emit('show::modal', 'yesterdaily');
|
||||
},
|
||||
async runYesterDailiesAction () {
|
||||
@@ -405,6 +410,10 @@ export default {
|
||||
this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true}),
|
||||
]);
|
||||
|
||||
if (this.levelBeforeYesterdailies < this.user.stats.lvl) {
|
||||
this.showLevelUpNotifications(this.user.stats.lvl);
|
||||
}
|
||||
|
||||
this.handleUserNotifications(this.user.notifications);
|
||||
this.scheduleNextCron();
|
||||
},
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
<script>
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
import * as Analytics from 'client/libs/analytics';
|
||||
import spellsMixin from 'client/mixins/spells';
|
||||
|
||||
import svgClose from 'assets/svg/close.svg';
|
||||
import svgGold from 'assets/svg/gold.svg';
|
||||
@@ -220,6 +221,7 @@
|
||||
import BalanceInfo from './balanceInfo.vue';
|
||||
import currencyMixin from './_currencyMixin';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
import buyMixin from 'client/mixins/buy';
|
||||
|
||||
import { mapState } from 'client/libs/store';
|
||||
|
||||
@@ -233,7 +235,7 @@
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
mixins: [currencyMixin, notifications],
|
||||
mixins: [currencyMixin, notifications, spellsMixin, buyMixin],
|
||||
components: {
|
||||
bModal,
|
||||
BalanceInfo,
|
||||
@@ -301,17 +303,11 @@
|
||||
this.$emit('change', $event);
|
||||
},
|
||||
buyItem () {
|
||||
if (this.genericPurchase) {
|
||||
this.$store.dispatch('shops:genericPurchase', {
|
||||
pinType: this.item.pinType,
|
||||
type: this.item.purchaseType,
|
||||
key: this.item.key,
|
||||
currency: this.item.currency,
|
||||
});
|
||||
|
||||
if (this.item.cast) {
|
||||
this.castStart(this.item);
|
||||
} else if (this.genericPurchase) {
|
||||
this.makeGenericPurchase(this.item);
|
||||
this.purchased(this.item.text);
|
||||
this.$root.$emit('buyModal::boughtItem', this.item);
|
||||
this.$root.$emit('playSound', 'Reward');
|
||||
}
|
||||
|
||||
this.$emit('buyPressed', this.item);
|
||||
|
||||
@@ -71,9 +71,6 @@
|
||||
p.span
|
||||
span {{ $t('marketing4Lead3-2') }}
|
||||
a.btn.btn-primary(href='/static/plans',target='_blank') {{ $t('contactUs') }}
|
||||
p.span
|
||||
span {{ $t('marketing4Lead3-3') }}
|
||||
a.btn.btn-primary(href='/static/videos') {{ $t('watchVideos') }}
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
min-height: 556px;
|
||||
}
|
||||
|
||||
.task-wrapper {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.task-wrapper + .reward-items {
|
||||
margin-top: 16px;
|
||||
}
|
||||
@@ -104,7 +109,7 @@
|
||||
.column-background {
|
||||
position: absolute;
|
||||
bottom: 32px;
|
||||
z-index: 7;
|
||||
z-index: 1;
|
||||
|
||||
&.initial-description {
|
||||
top: 30%;
|
||||
@@ -160,19 +165,25 @@
|
||||
<script>
|
||||
import Task from './task';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import throttle from 'lodash/throttle';
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
|
||||
import sortable from 'client/directives/sortable.directive';
|
||||
import buyMixin from 'client/mixins/buy';
|
||||
import { mapState, mapActions } from 'client/libs/store';
|
||||
import shopItem from '../shops/shopItem';
|
||||
|
||||
import { shouldDo } from 'common/script/cron';
|
||||
import inAppRewards from 'common/script/libs/inAppRewards';
|
||||
import spells from 'common/script/content/spells';
|
||||
|
||||
import habitIcon from 'assets/svg/habit.svg';
|
||||
import dailyIcon from 'assets/svg/daily.svg';
|
||||
import todoIcon from 'assets/svg/todo.svg';
|
||||
import rewardIcon from 'assets/svg/reward.svg';
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
import shopItem from '../shops/shopItem';
|
||||
import throttle from 'lodash/throttle';
|
||||
import sortable from 'client/directives/sortable.directive';
|
||||
|
||||
export default {
|
||||
mixins: [buyMixin],
|
||||
components: {
|
||||
Task,
|
||||
bModal,
|
||||
@@ -252,8 +263,30 @@ export default {
|
||||
},
|
||||
inAppRewards () {
|
||||
let watchRefresh = this.forceRefresh; // eslint-disable-line
|
||||
let rewards = inAppRewards(this.user);
|
||||
|
||||
return inAppRewards(this.user);
|
||||
|
||||
// Add season rewards if user is affected
|
||||
// @TODO: Add buff coniditional
|
||||
const seasonalSkills = {
|
||||
snowball: 'salt',
|
||||
spookySparkles: 'opaquePotion',
|
||||
shinySeed: 'petalFreePotion',
|
||||
seafoam: 'sand',
|
||||
};
|
||||
|
||||
for (let key in seasonalSkills) {
|
||||
if (this.user.stats.buffs[key]) {
|
||||
let debuff = seasonalSkills[key];
|
||||
let item = Object.assign({}, spells.special[debuff]);
|
||||
item.text = item.text();
|
||||
item.notes = item.notes();
|
||||
item.class = `shop_${key}`;
|
||||
rewards.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return rewards;
|
||||
},
|
||||
hasRewardsList () {
|
||||
return this.isUser === true && this.type === 'reward' && this.activeFilters[this.type].label !== 'custom';
|
||||
@@ -378,6 +411,14 @@ export default {
|
||||
}
|
||||
},
|
||||
openBuyDialog (rewardItem) {
|
||||
// Buy armoire and health potions immediately
|
||||
let itemsToPurchaseImmediately = ['potion', 'armoire'];
|
||||
if (itemsToPurchaseImmediately.indexOf(rewardItem.key) !== -1) {
|
||||
this.makeGenericPurchase(rewardItem);
|
||||
this.$emit('buyPressed', rewardItem);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rewardItem.purchaseType !== 'gear' || !rewardItem.locked) {
|
||||
this.$emit('openBuyDialog', rewardItem);
|
||||
}
|
||||
|
||||
@@ -148,14 +148,13 @@ div(v-if='user.stats.lvl > 10')
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import isArray from 'lodash/isArray';
|
||||
import bPopover from 'bootstrap-vue/lib/directives/popover';
|
||||
|
||||
import spells from '../../../common/script/content/spells';
|
||||
|
||||
import { mapState } from 'client/libs/store';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
import spellsMixin from 'client/mixins/spells';
|
||||
import Drawer from 'client/components/ui/drawer';
|
||||
import MouseMoveDirective from 'client/directives/mouseposition.directive';
|
||||
|
||||
@@ -164,7 +163,7 @@ import quests from 'common/script/content/quests';
|
||||
import { CONSTANTS, setLocalSetting, getLocalSetting } from 'client/libs/userlocalManager';
|
||||
|
||||
export default {
|
||||
mixins: [notifications],
|
||||
mixins: [notifications, spellsMixin],
|
||||
components: {
|
||||
Drawer,
|
||||
},
|
||||
@@ -245,122 +244,6 @@ export default {
|
||||
|
||||
return notes;
|
||||
},
|
||||
async castStart (spell) {
|
||||
if (this.$store.state.spellOptions.castingSpell) {
|
||||
this.castCancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.user.stats.mp < spell.mana) return this.text(this.$t('notEnoughMana'));
|
||||
|
||||
if (spell.immediateUse && this.user.stats.gp < spell.value) {
|
||||
return this.text('Not enough gold.');
|
||||
}
|
||||
|
||||
this.potionClickMode = true;
|
||||
this.applyingAction = true;
|
||||
this.$store.state.spellOptions.castingSpell = true;
|
||||
this.spell = spell;
|
||||
|
||||
if (spell.target === 'self') {
|
||||
this.castEnd(null, spell.target);
|
||||
} else if (spell.target === 'party') {
|
||||
if (!this.user.party._id) {
|
||||
let party = [this.user];
|
||||
this.castEnd(party, spell.target);
|
||||
return;
|
||||
}
|
||||
|
||||
let party = this.$store.state.party.members;
|
||||
party = isArray(party) ? party : [];
|
||||
party = party.concat(this.user);
|
||||
this.castEnd(party, spell.target);
|
||||
} else if (spell.target === 'tasks') {
|
||||
let userTasks = this.$store.state.tasks.data;
|
||||
// exclude rewards
|
||||
let tasks = userTasks.habits
|
||||
.concat(userTasks.dailys)
|
||||
.concat(userTasks.todos);
|
||||
// exclude challenge and group plan tasks
|
||||
tasks = tasks.filter((task) => {
|
||||
if (task.challenge && task.challenge.id && !task.challenge.broken) return false;
|
||||
if (task.group && task.group.id && !task.group.broken) return false;
|
||||
return true;
|
||||
});
|
||||
this.castEnd(tasks, spell.target);
|
||||
}
|
||||
},
|
||||
async castEnd (target, type) {
|
||||
if (!this.$store.state.spellOptions.castingSpell) return;
|
||||
let beforeQuestProgress = this.questProgress();
|
||||
|
||||
if (!this.applyingAction) return 'No applying action';
|
||||
|
||||
if (this.spell.target !== type) return this.text(this.$t('invalidTarget'));
|
||||
if (target && target.type && target.type === 'reward') return this.text(this.$t('invalidTarget'));
|
||||
if (target && target.challenge && target.challenge.id) return this.text(this.$t('invalidTarget'));
|
||||
if (target && target.group && target.group.id) return this.text(this.$t('invalidTarget'));
|
||||
|
||||
// @TODO: just call castCancel?
|
||||
this.$store.state.spellOptions.castingSpell = false;
|
||||
this.potionClickMode = false;
|
||||
|
||||
this.spell.cast(this.user, target);
|
||||
// User.save(); // @TODO:
|
||||
|
||||
let spell = this.spell;
|
||||
let targetId = target ? target._id : null;
|
||||
this.spell = null;
|
||||
this.applyingAction = false;
|
||||
|
||||
let spellUrl = `/api/v3/user/class/cast/${spell.key}`;
|
||||
if (targetId) spellUrl += `?targetId=${targetId}`;
|
||||
|
||||
await axios.post(spellUrl);
|
||||
let msg = this.$t('youCast', {
|
||||
spell: spell.text(),
|
||||
});
|
||||
|
||||
switch (type) {
|
||||
case 'task':
|
||||
msg = this.$t('youCastTarget', {
|
||||
spell: spell.text(),
|
||||
target: target.text,
|
||||
});
|
||||
break;
|
||||
case 'user':
|
||||
msg = this.$t('youCastTarget', {
|
||||
spell: spell.text(),
|
||||
target: target.profile.name,
|
||||
});
|
||||
break;
|
||||
case 'party':
|
||||
msg = this.$t('youCastParty', {
|
||||
spell: spell.text(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
this.markdown(msg); // @TODO: mardown directive?
|
||||
// @TODO:
|
||||
let questProgress = this.questProgress() - beforeQuestProgress;
|
||||
if (questProgress > 0) {
|
||||
let userQuest = this.quests[this.user.party.quest.key];
|
||||
if (userQuest.boss) {
|
||||
this.quest('questDamage', questProgress.toFixed(1));
|
||||
} else if (userQuest.collection && userQuest.collect) {
|
||||
this.quest('questCollection', questProgress);
|
||||
}
|
||||
}
|
||||
// @TOOD: User.sync();
|
||||
},
|
||||
castCancel () {
|
||||
this.potionClickMode = false;
|
||||
this.applyingAction = false;
|
||||
this.spell = null;
|
||||
document.querySelector('body').style.cursor = 'initial';
|
||||
this.$store.state.spellOptions.castingSpell = false;
|
||||
},
|
||||
questProgress () {
|
||||
let user = this.user;
|
||||
if (!user.party.quest) return 0;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
.task-notes.small-text(v-markdown="task.notes")
|
||||
.checklist(v-if="canViewchecklist")
|
||||
label.custom-control.custom-checkbox.checklist-item(
|
||||
v-if='!castingSpell',
|
||||
v-for="item in task.checklist", :class="{'checklist-item-done': item.completed}",
|
||||
)
|
||||
input.custom-control-input(type="checkbox", :checked="item.completed", @change="toggleChecklistItem(item)")
|
||||
@@ -339,7 +340,10 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
castingSpell: 'spellOptions.castingSpell',
|
||||
}),
|
||||
...mapGetters({
|
||||
getTagsFor: 'tasks:getTagsFor',
|
||||
getTaskClasses: 'tasks:getTaskClasses',
|
||||
@@ -390,6 +394,7 @@ export default {
|
||||
methods: {
|
||||
...mapActions({scoreChecklistItem: 'tasks:scoreChecklistItem'}),
|
||||
toggleChecklistItem (item) {
|
||||
if (this.castingSpell) return;
|
||||
item.completed = !item.completed;
|
||||
this.scoreChecklistItem({taskId: this.task._id, itemId: item.id});
|
||||
},
|
||||
@@ -407,6 +412,8 @@ export default {
|
||||
this.$root.$emit('castEnd', task, 'task', e);
|
||||
},
|
||||
async score (direction) {
|
||||
if (this.castingSpell) return;
|
||||
|
||||
// TODO move to an action
|
||||
const Content = this.$store.state.content;
|
||||
const user = this.user;
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
max-width: 80%;
|
||||
|
||||
@media screen and (min-width: 1241px) {
|
||||
max-width: 968px;
|
||||
max-width: 978px;
|
||||
// 16.67% is the width of the .col-2 sidebar
|
||||
left: calc((100% + 16.67% - 968px) / 2);
|
||||
left: calc((100% + 16.67% - 978px) / 2);
|
||||
right: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
.fill-height {
|
||||
height: 38px; // button + margin + padding
|
||||
}
|
||||
|
||||
.item-rows {
|
||||
margin-right: -24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
14
website/client/mixins/buy.js
Normal file
@@ -0,0 +1,14 @@
|
||||
export default {
|
||||
methods: {
|
||||
makeGenericPurchase (item) {
|
||||
this.$store.dispatch('shops:genericPurchase', {
|
||||
pinType: item.pinType,
|
||||
type: item.purchaseType,
|
||||
key: item.key,
|
||||
currency: item.currency,
|
||||
});
|
||||
this.$root.$emit('buyModal::boughtItem', item);
|
||||
this.$root.$emit('playSound', 'Reward');
|
||||
},
|
||||
},
|
||||
};
|
||||
128
website/client/mixins/spells.js
Normal file
@@ -0,0 +1,128 @@
|
||||
import axios from 'axios';
|
||||
import isArray from 'lodash/isArray';
|
||||
|
||||
// @TODO: Let's separate some of the business logic out of Vue if possible
|
||||
export default {
|
||||
methods: {
|
||||
async castStart (spell) {
|
||||
if (this.$store.state.spellOptions.castingSpell) {
|
||||
this.castCancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.user.stats.mp < spell.mana) return this.text(this.$t('notEnoughMana'));
|
||||
|
||||
if (spell.immediateUse && this.user.stats.gp < spell.value) {
|
||||
return this.text('Not enough gold.');
|
||||
}
|
||||
|
||||
this.potionClickMode = true;
|
||||
this.applyingAction = true;
|
||||
this.$store.state.spellOptions.castingSpell = true;
|
||||
this.spell = spell;
|
||||
|
||||
if (spell.target === 'self') {
|
||||
this.castEnd(null, spell.target);
|
||||
} else if (spell.target === 'party') {
|
||||
if (!this.user.party._id) {
|
||||
let party = [this.user];
|
||||
this.castEnd(party, spell.target);
|
||||
return;
|
||||
}
|
||||
|
||||
let party = this.$store.state.party.members;
|
||||
party = isArray(party) ? party : [];
|
||||
party = party.concat(this.user);
|
||||
this.castEnd(party, spell.target);
|
||||
} else if (spell.target === 'tasks') {
|
||||
let userTasks = this.$store.state.tasks.data;
|
||||
// exclude rewards
|
||||
let tasks = userTasks.habits
|
||||
.concat(userTasks.dailys)
|
||||
.concat(userTasks.todos);
|
||||
// exclude challenge and group plan tasks
|
||||
tasks = tasks.filter((task) => {
|
||||
if (task.challenge && task.challenge.id && !task.challenge.broken) return false;
|
||||
if (task.group && task.group.id && !task.group.broken) return false;
|
||||
return true;
|
||||
});
|
||||
this.castEnd(tasks, spell.target);
|
||||
}
|
||||
},
|
||||
async castEnd (target, type) {
|
||||
if (!this.$store.state.spellOptions.castingSpell) return;
|
||||
let beforeQuestProgress;
|
||||
if (this.spell.target === 'party') beforeQuestProgress = this.questProgress();
|
||||
|
||||
if (!this.applyingAction) return 'No applying action';
|
||||
|
||||
if (this.spell.target !== type) return this.text(this.$t('invalidTarget'));
|
||||
if (target && target.type && target.type === 'reward') return this.text(this.$t('invalidTarget'));
|
||||
if (target && target.challenge && target.challenge.id) return this.text(this.$t('invalidTarget'));
|
||||
if (target && target.group && target.group.id) return this.text(this.$t('invalidTarget'));
|
||||
|
||||
// @TODO: just call castCancel?
|
||||
this.$store.state.spellOptions.castingSpell = false;
|
||||
this.potionClickMode = false;
|
||||
|
||||
this.spell.cast(this.user, target);
|
||||
// User.save(); // @TODO:
|
||||
|
||||
let spell = this.spell;
|
||||
let targetId = target ? target._id : null;
|
||||
this.spell = null;
|
||||
this.applyingAction = false;
|
||||
|
||||
let spellUrl = `/api/v3/user/class/cast/${spell.key}`;
|
||||
if (targetId) spellUrl += `?targetId=${targetId}`;
|
||||
|
||||
let spellText = typeof spell.text === 'function' ? spell.text() : spell.text;
|
||||
|
||||
await axios.post(spellUrl);
|
||||
let msg = this.$t('youCast', {
|
||||
spell: spellText,
|
||||
});
|
||||
|
||||
switch (type) {
|
||||
case 'task':
|
||||
msg = this.$t('youCastTarget', {
|
||||
spell: spellText,
|
||||
target: target.text,
|
||||
});
|
||||
break;
|
||||
case 'user':
|
||||
msg = this.$t('youCastTarget', {
|
||||
spell: spellText,
|
||||
target: target.profile.name,
|
||||
});
|
||||
break;
|
||||
case 'party':
|
||||
msg = this.$t('youCastParty', {
|
||||
spell: spellText,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
this.markdown(msg); // @TODO: mardown directive?
|
||||
// @TODO:
|
||||
if (!beforeQuestProgress) return;
|
||||
let questProgress = this.questProgress() - beforeQuestProgress;
|
||||
if (questProgress > 0) {
|
||||
let userQuest = this.quests[this.user.party.quest.key];
|
||||
if (userQuest.boss) {
|
||||
this.quest('questDamage', questProgress.toFixed(1));
|
||||
} else if (userQuest.collection && userQuest.collect) {
|
||||
this.quest('questCollection', questProgress);
|
||||
}
|
||||
}
|
||||
// @TOOD: User.sync();
|
||||
},
|
||||
castCancel () {
|
||||
this.potionClickMode = false;
|
||||
this.applyingAction = false;
|
||||
this.spell = null;
|
||||
document.querySelector('body').style.cursor = 'initial';
|
||||
this.$store.state.spellOptions.castingSpell = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -9,14 +9,15 @@ import * as Analytics from 'client/libs/analytics';
|
||||
import ParentPage from './components/parentPage';
|
||||
|
||||
// Static Pages
|
||||
const StaticWrapper = () => import(/* webpackChunkName: "static" */'./components/static/staticWrapper');
|
||||
const StaticWrapper = () => import(/* webpackChunkName: "entry" */'./components/static/staticWrapper');
|
||||
const HomePage = () => import(/* webpackChunkName: "entry" */'./components/static/home');
|
||||
|
||||
const AppPage = () => import(/* webpackChunkName: "static" */'./components/static/app');
|
||||
const ClearBrowserDataPage = () => import(/* webpackChunkName: "static" */'./components/static/clearBrowserData');
|
||||
const CommunityGuidelinesPage = () => import(/* webpackChunkName: "static" */'./components/static/communityGuidelines');
|
||||
const ContactPage = () => import(/* webpackChunkName: "static" */'./components/static/contact');
|
||||
const FAQPage = () => import(/* webpackChunkName: "static" */'./components/static/faq');
|
||||
const FeaturesPage = () => import(/* webpackChunkName: "static" */'./components/static/features');
|
||||
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');
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
"shortName": "Short Name",
|
||||
"shortNamePlaceholder": "What short tag should be used to identify your Challenge?",
|
||||
"updateChallenge": "Update Challenge",
|
||||
"haveNoChallenges": "You don't have any Challenges",
|
||||
"haveNoChallenges": "This group has no Challenges",
|
||||
"loadMore": "Load More",
|
||||
"exportChallengeCsv": "Export Challenge",
|
||||
"editingChallenge": "Editing Challenge",
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
"optOutOfPMs": "Opt Out",
|
||||
"chooseClass": "Choose your Class",
|
||||
"chooseClassLearnMarkdown": "[Learn more about Habitica's class system](http://habitica.wikia.com/wiki/Class_System)",
|
||||
"optOutOfClassesText": "Can't be bothered with classes? Want to choose later? Opt out - you'll be a warrior with no special abilities. You can read about the class system later on the wiki and enable classes at any time under User -> Stats.",
|
||||
"optOutOfClassesText": "Can't be bothered with classes? Want to choose later? Opt out - you'll be a warrior with no special abilities. You can read about the class system later on the wiki and enable classes at any time under User Icon > Settings.",
|
||||
"selectClass": "Select <%= heroClass %>",
|
||||
"select": "Select",
|
||||
"stealth": "Stealth",
|
||||
@@ -196,7 +196,7 @@
|
||||
"int": "INT",
|
||||
"showQuickAllocation": "Show stat allocation",
|
||||
"hideQuickAllocation": "Hide stat allocation",
|
||||
"quickAllocationLevelPopover": "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 found in User -> Stats.",
|
||||
"quickAllocationLevelPopover": "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 found in User Icon > Stats.",
|
||||
"invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
|
||||
"notEnoughAttrPoints": "You don't have enough attribute points.",
|
||||
"style": "Style",
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
"error": "Error",
|
||||
"menu": "Menu",
|
||||
"notifications": "Notifications",
|
||||
"noNotifications": "You have no new messages.",
|
||||
"noNotifications": "You have no notifications.",
|
||||
"clear": "Clear",
|
||||
"endTour": "End Tour",
|
||||
"audioTheme": "Audio Theme",
|
||||
|
||||
@@ -358,7 +358,7 @@
|
||||
"guildSummaryPlaceholder": "Write a short description advertising your Guild to other Habiticans. What is the main purpose of your Guild and why should people join it? Try to include useful keywords in the summary so that Habiticans can easily find it when they search!",
|
||||
"groupDescription": "Description",
|
||||
"guildDescriptionPlaceholder": "Use this section to go into more detail about everything that Guild members should know about your Guild. Useful tips, helpful links, and encouraging statements all go here!",
|
||||
"markdownFormattingHelp": "Markdown formatting help",
|
||||
"markdownFormattingHelp": "[Markdown formatting help](http://habitica.wikia.com/wiki/Markdown_Cheat_Sheet)",
|
||||
"partyDescriptionPlaceholder": "This is our party's description. It describes what we do in this party. If you want to learn more about what we do in this party, read the description. Party on.",
|
||||
"guildGemCostInfo": "A Gem cost promotes high quality Guilds and is transferred into your Guild's bank.",
|
||||
"noGuildsTitle": "You aren't a member of any Guilds.",
|
||||
@@ -391,7 +391,7 @@
|
||||
"reverseChat": "Reverse Chat",
|
||||
"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.",
|
||||
"participantDesc": "Once all members have either accepted or declined, the Quest begins. Only those who clicked 'accept' will be able to participate in the Quest and receive the rewards.",
|
||||
"groupGems": "Group Gems",
|
||||
"groupGemsDesc": "Guild Gems can be spent to make Challenges! In the future, you will be able to add more Guild Gems."
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"useGems": "If you've got your eye on a pet, but can't wait any longer for it to drop, use Gems in <strong>Inventory > Market</strong> to buy one!",
|
||||
"hatchAPot": "Hatch a new <%= potion %> <%= egg %>?",
|
||||
"hatchedPet": "You hatched a new <%= potion %> <%= egg %>!",
|
||||
"hatchedPetGeneric": "You hatched a new pet!",
|
||||
"displayNow": "Display Now",
|
||||
"displayLater": "Display Later",
|
||||
"petNotOwned": "You do not own this pet.",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"unlockedReward": "You have received <%= reward %>",
|
||||
"earnedRewardForDevotion": "You have earned <%= reward %> for being committed to improving your life.",
|
||||
"nextRewardUnlocksIn": "Check-ins until your next prize: <%= numberOfCheckinsLeft %>",
|
||||
"nextRewardUnlocksIn": "Check-Ins until your next prize: <%= numberOfCheckinsLeft %>",
|
||||
"awesome": "Awesome!",
|
||||
"totalCount": "<%= count %> total count",
|
||||
"countLeft": "Check-ins until next reward: <%= count %>",
|
||||
"countLeft": "Check-Ins until next reward: <%= count %>",
|
||||
"incentivesDescription": "When it comes to building habits, consistency is key. Each day you check-in, you get closer to a prize.",
|
||||
"totalCheckins": "<%= count %> Check-Ins",
|
||||
"checkinEarned": "Your Check-In Counter went up!",
|
||||
|
||||
BIN
website/static/emails/images/10-days-recapture-v1.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
website/static/emails/images/3-days-1-month-recapture-v1.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
website/static/emails/images/PROMO-Enchanted-Armoire-v1.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
website/static/emails/images/android-promo-v1.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
website/static/emails/images/iphone-promo-v1.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
website/static/emails/images/one-day-v1.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
website/static/emails/images/spring-2015-00-v1.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
website/static/emails/images/spring-2015-01-v1.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
BIN
website/static/emails/images/subscription-begins-v1.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |