mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
Merge develop to release (#9115)
* Some random quick (#9111) * Switch group button directions * Allowed admins to export challenges * Added scoping to some stable styles * Fixed challenge cloning * Tasks tags (#9112) * Added auto apply and exit * Add challenge tag editing * Fixed lint * Skill fixes (#9113) * Added local storage setting for spell drawer * Added new spell styles * Fixed typo * Reset local creds if access is denied (#9114)
This commit is contained in:
@@ -144,6 +144,16 @@ export default {
|
||||
return response;
|
||||
}, (error) => {
|
||||
if (error.response.status >= 400) {
|
||||
// Check for conditions to reset the user auth
|
||||
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
|
||||
if (invalidUserMessage.indexOf(error.response.data.message) !== -1) {
|
||||
localStorage.removeItem('habit-mobile-settings');
|
||||
localStorage.removeItem('hello');
|
||||
this.$store.state.isUserLoggedIn = false;
|
||||
window.location.href = '/static/home';
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
button.btn.btn-secondary(v-once, @click='edit()') {{$t('editChallenge')}}
|
||||
div(v-if='isLeader')
|
||||
button.btn.btn-danger(v-once, @click='closeChallenge()') {{$t('endChallenge')}}
|
||||
div(v-if='isLeader')
|
||||
div(v-if='isLeader || isAdmin')
|
||||
button.btn.btn-secondary(v-once, @click='exportChallengeCsv()') {{$t('exportChallengeCsv')}}
|
||||
div(v-if='isLeader')
|
||||
button.btn.btn-secondary(v-once, @click='cloneChallenge()') {{$t('clone')}}
|
||||
@@ -246,6 +246,9 @@ export default {
|
||||
if (!this.challenge.leader) return false;
|
||||
return this.user._id === this.challenge.leader._id;
|
||||
},
|
||||
isAdmin () {
|
||||
return Boolean(this.user.contributor.admin);
|
||||
},
|
||||
canJoin () {
|
||||
return !this.isMember;
|
||||
},
|
||||
@@ -403,6 +406,7 @@ export default {
|
||||
cloneChallenge () {
|
||||
this.cloning = true;
|
||||
this.$store.state.challengeOptions.tasksToClone = this.tasksByType;
|
||||
this.$store.state.challengeOptions.workingChallenge = Object.assign({}, this.$store.state.challengeOptions.workingChallenge, this.challenge);
|
||||
this.$root.$emit('show::modal', 'challenge-modal');
|
||||
},
|
||||
},
|
||||
|
||||
@@ -31,11 +31,13 @@
|
||||
.row.new-message-row
|
||||
textarea(:placeholder="!isParty ? $t('chatPlaceholder') : $t('partyChatPlaceholder')", v-model='newMessage', @keydown='updateCarretPosition')
|
||||
autocomplete(:text='newMessage', v-on:select="selectedAutocomplete", :coords='coords', :chat='group.chat')
|
||||
button.btn.btn-secondary.send-chat.float-left(v-once, @click='sendMessage()') {{ $t('send') }}
|
||||
|
||||
.row
|
||||
.col-6
|
||||
button.btn.btn-secondary.float-left.fetch(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }}
|
||||
button.btn.btn-secondary.float-left(v-once, @click='reverseChat()') {{ $t('reverseChat') }}
|
||||
.col-6
|
||||
button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }}
|
||||
|
||||
.row.community-guidelines(v-if='!communityGuidelinesAccepted')
|
||||
div.col-8(v-once, v-html="$t('communityGuidelinesIntro')")
|
||||
@@ -301,13 +303,6 @@
|
||||
.new-message-row {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.send-chat {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-up .svg-icon, .toggle-down .svg-icon {
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
|
||||
.row
|
||||
.col-6
|
||||
button.btn.btn-secondary.send-chat.float-left(v-once, @click='sendMessage()') {{ $t('send') }}
|
||||
button.btn.btn-secondary.float-left.fetch(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }}
|
||||
button.btn.btn-secondary.float-left(v-once, @click='reverseChat()') {{ $t('reverseChat') }}
|
||||
.col-6
|
||||
button.btn.btn-secondary.float-right.fetch(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }}
|
||||
button.btn.btn-secondary.float-right(v-once, @click='reverseChat()') {{ $t('reverseChat') }}
|
||||
button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }}
|
||||
|
||||
.row.community-guidelines(v-if='!communityGuidelinesAccepted')
|
||||
div.col-8(v-once, v-html="$t('communityGuidelinesIntro')")
|
||||
|
||||
@@ -248,6 +248,22 @@
|
||||
div.popover-content {{ $t('clickOnPetToFeed', {foodName: currentDraggingFood.text() }) }}
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.group {
|
||||
height: 130px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pet-row {
|
||||
max-width: 100%;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.item {
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
@import '~client/assets/scss/modal.scss';
|
||||
@@ -274,20 +290,6 @@
|
||||
top: -16px !important;
|
||||
}
|
||||
|
||||
.group {
|
||||
height: 130px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pet-row {
|
||||
max-width: 100%;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.item {
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.hatchablePopover {
|
||||
width: 180px
|
||||
}
|
||||
|
||||
@@ -21,16 +21,13 @@ div(v-if='user.stats.lvl > 10')
|
||||
@click='castStart(skill)',
|
||||
v-for='(skill, key) in spells[user.stats.class]',
|
||||
v-if='user.stats.lvl >= skill.lvl',
|
||||
popover-trigger='mouseenter',
|
||||
popover-placement='top',
|
||||
:popover='skillNotes(skill)')
|
||||
v-b-popover.hover.auto='skill.notes()')
|
||||
.spell.col-12.row
|
||||
.col-8.details
|
||||
a(:class='{"disabled": spellDisabled(key)}')
|
||||
p.title {{skill.text()}}
|
||||
p.notes {{skill.notes()}}
|
||||
div.img(:class='`shop_${skill.key} shop-sprite item-img`')
|
||||
span.title {{skill.text()}}
|
||||
.col-4.mana
|
||||
.img(:class='`shop_${skill.key} shop-sprite item-img`')
|
||||
.mana-text
|
||||
.svg-icon(v-html="icons.mana")
|
||||
div {{skill.mana}}
|
||||
@@ -52,23 +49,33 @@ div(v-if='user.stats.lvl > 10')
|
||||
|
||||
.spell:hover {
|
||||
cursor: pointer;
|
||||
border: solid 2px #50b5e9;
|
||||
}
|
||||
|
||||
.spell {
|
||||
background: #ffffff;
|
||||
border: solid 2px transparent;
|
||||
margin-bottom: 1em;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
border-radius: 2px;
|
||||
border-radius: 1000px;
|
||||
color: #4e4a57;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.details {
|
||||
text-align: left;
|
||||
padding-top: .5em;
|
||||
|
||||
p {
|
||||
margin-bottom: .5em;
|
||||
.img {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
padding-bottom: .7em;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.notes {
|
||||
@@ -83,6 +90,7 @@ div(v-if='user.stats.lvl > 10')
|
||||
|
||||
.mana-text {
|
||||
margin-bottom: .2em;
|
||||
padding-top: 1.1em;
|
||||
|
||||
div {
|
||||
display: inline-block;
|
||||
@@ -142,6 +150,7 @@ div(v-if='user.stats.lvl > 10')
|
||||
<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';
|
||||
|
||||
@@ -152,6 +161,7 @@ import MouseMoveDirective from 'client/directives/mouseposition.directive';
|
||||
|
||||
import mana from 'assets/svg/mana.svg';
|
||||
import quests from 'common/script/content/quests';
|
||||
import { CONSTANTS, setLocalSetting, getLocalSetting } from 'client/libs/userlocalManager';
|
||||
|
||||
export default {
|
||||
mixins: [notifications],
|
||||
@@ -160,6 +170,7 @@ export default {
|
||||
},
|
||||
directives: {
|
||||
mousePosition: MouseMoveDirective,
|
||||
bPopover,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -183,6 +194,11 @@ export default {
|
||||
if (keyEvent.keyCode !== 27) return;
|
||||
this.castCancel();
|
||||
});
|
||||
|
||||
const spellDrawerState = getLocalSetting(CONSTANTS.keyConstants.SPELL_DRAWER_STATE);
|
||||
if (spellDrawerState === CONSTANTS.valueConstants.SPELL_DRAWER_CLOSED) {
|
||||
this.$store.state.spellOptions.spellDrawOpen = false;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
@@ -191,8 +207,15 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
drawerToggled (openChanged) {
|
||||
this.$store.state.spellOptions.spellDrawOpen = openChanged;
|
||||
drawerToggled (newState) {
|
||||
this.$store.state.spellOptions.spellDrawOpen = newState;
|
||||
|
||||
if (newState) {
|
||||
setLocalSetting(CONSTANTS.keyConstants.SPELL_DRAWER_STATE, CONSTANTS.valueConstants.SPELL_DRAWER_OPEN);
|
||||
return;
|
||||
}
|
||||
|
||||
setLocalSetting(CONSTANTS.keyConstants.SPELL_DRAWER_STATE, CONSTANTS.valueConstants.SPELL_DRAWER_CLOSED);
|
||||
},
|
||||
spellDisabled (skill) {
|
||||
if (skill === 'frost' && this.user.stats.buffs.streaks) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
.col-4.offset-4
|
||||
.input-group
|
||||
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
|
||||
.filter-panel(v-if="isFilterPanelOpen")
|
||||
.filter-panel(v-if="isFilterPanelOpen", v-on:mouseleave="checkMouseOver")
|
||||
.tags-category.d-flex(
|
||||
v-for="tagsType in tagsByType",
|
||||
v-if="tagsType.tags.length > 0 || tagsType.key === 'tags'",
|
||||
@@ -19,17 +19,17 @@
|
||||
)
|
||||
.tags-header
|
||||
strong(v-once) {{ $t(tagsType.key) }}
|
||||
a.d-block(v-if="tagsType.key === 'tags' && !editingTags", @click="editTags()") {{ $t('editTags2') }}
|
||||
a.d-block(v-if="tagsType.key !== 'groups' && !editingTags", @click="editTags(tagsType.key)") {{ $t('editTags2') }}
|
||||
.tags-list.container
|
||||
.row(:class="{'no-gutters': !editingTags}")
|
||||
template(v-if="editingTags && tagsType.key === 'tags'")
|
||||
.col-6(v-for="(tag, tagIndex) in tagsSnap")
|
||||
template(v-if="editingTags && tagsType.key !== 'groups'")
|
||||
.col-6(v-for="(tag, tagIndex) in tagsSnap[tagsType.key]")
|
||||
.inline-edit-input-group.tag-edit-item.input-group
|
||||
input.tag-edit-input.inline-edit-input.form-control(type="text", v-model="tag.name")
|
||||
span.input-group-btn(@click="removeTag(tagIndex)")
|
||||
span.input-group-btn(@click="removeTag(tagIndex, tagsType.key)")
|
||||
.svg-icon.destroy-icon(v-html="icons.destroy")
|
||||
.col-6
|
||||
input.new-tag-item.edit-tag-item.inline-edit-input.form-control(type="text", :placeholder="$t('newTag')", @keydown.enter="addTag($event)", v-model="newTag")
|
||||
.col-6(v-if="tagsType.key === 'tags'")
|
||||
input.new-tag-item.edit-tag-item.inline-edit-input.form-control(type="text", :placeholder="$t('newTag')", @keydown.enter="addTag($event, tagsType.key)", v-model="newTag")
|
||||
template(v-else)
|
||||
.col-6(v-for="(tag, tagIndex) in tagsType.tags")
|
||||
label.custom-control.custom-checkbox
|
||||
@@ -50,7 +50,6 @@
|
||||
.float-left
|
||||
a.btn-filters-danger(@click="resetFilters()", v-once) {{ $t('resetFilters') }}
|
||||
.float-right
|
||||
a.mr-3.btn-filters-primary(@click="applyFilters()", v-once) {{ $t('applyFilters') }}
|
||||
a.btn-filters-secondary(@click="closeFilterPanel()", v-once) {{ $t('cancel') }}
|
||||
span.input-group-btn
|
||||
button.btn.btn-secondary.filter-button(
|
||||
@@ -322,7 +321,10 @@ export default {
|
||||
}),
|
||||
selectedTags: [],
|
||||
temporarilySelectedTags: [],
|
||||
tagsSnap: null, // tags snapshot when being edited
|
||||
tagsSnap: {
|
||||
tags: [],
|
||||
challenges: [],
|
||||
}, // tags snapshot when being edited
|
||||
editingTags: false,
|
||||
newTag: null,
|
||||
editingTask: null,
|
||||
@@ -368,26 +370,37 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions({setUser: 'user:set'}),
|
||||
checkMouseOver: throttle(function throttleSearch () {
|
||||
this.closeFilterPanel();
|
||||
}, 250),
|
||||
editTags () {
|
||||
// clone the arrays being edited so that we can revert if needed
|
||||
this.tagsSnap = this.tagsByType.user.tags.slice();
|
||||
this.tagsSnap.tags = this.tagsByType.user.tags.slice();
|
||||
this.tagsSnap.challenges = this.tagsByType.challenges.tags.slice();
|
||||
this.editingTags = true;
|
||||
},
|
||||
addTag () {
|
||||
this.tagsSnap.push({id: uuid.v4(), name: this.newTag});
|
||||
addTag (eventObj, key) {
|
||||
this.tagsSnap[key].push({id: uuid.v4(), name: this.newTag});
|
||||
this.newTag = null;
|
||||
},
|
||||
removeTag (index) {
|
||||
this.tagsSnap.splice(index, 1);
|
||||
removeTag (index, key) {
|
||||
this.$delete(this.tagsSnap[key], index);
|
||||
},
|
||||
saveTags () {
|
||||
if (this.newTag) this.addTag();
|
||||
this.setUser({tags: this.tagsSnap});
|
||||
|
||||
this.tagsByType.user.tags = this.tagsSnap.tags;
|
||||
this.tagsByType.challenges.tags = this.tagsSnap.challenges;
|
||||
|
||||
this.setUser({tags: this.tagsSnap.tags.concat(this.tagsSnap.challenges)});
|
||||
this.cancelTagsEditing();
|
||||
},
|
||||
cancelTagsEditing () {
|
||||
this.editingTags = false;
|
||||
this.tagsSnap = null;
|
||||
this.tagsSnap = {
|
||||
tags: [],
|
||||
challenges: [],
|
||||
};
|
||||
this.newTag = null;
|
||||
},
|
||||
editTask (task) {
|
||||
@@ -430,7 +443,6 @@ export default {
|
||||
applyFilters () {
|
||||
const temporarilySelectedTags = this.temporarilySelectedTags;
|
||||
this.selectedTags = temporarilySelectedTags.slice();
|
||||
this.closeFilterPanel();
|
||||
},
|
||||
toggleTag (tag) {
|
||||
const temporarilySelectedTags = this.temporarilySelectedTags;
|
||||
@@ -440,6 +452,8 @@ export default {
|
||||
} else {
|
||||
temporarilySelectedTags.splice(tagI, 1);
|
||||
}
|
||||
|
||||
this.applyFilters();
|
||||
},
|
||||
isTagSelected (tag) {
|
||||
const tagId = tag.id;
|
||||
|
||||
23
website/client/libs/userlocalManager.js
Normal file
23
website/client/libs/userlocalManager.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const CONSTANTS = {
|
||||
keyConstants: {
|
||||
SPELL_DRAWER_STATE: 'spell-drawer-state',
|
||||
},
|
||||
valueConstants: {
|
||||
SPELL_DRAWER_CLOSED: 'spell-drawer-closed',
|
||||
SPELL_DRAWER_OPEN: 'spell-drawer-open',
|
||||
},
|
||||
};
|
||||
|
||||
function setLocalSetting (key, value) {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
|
||||
function getLocalSetting (key) {
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
|
||||
export {
|
||||
CONSTANTS,
|
||||
getLocalSetting,
|
||||
setLocalSetting,
|
||||
};
|
||||
@@ -26,7 +26,7 @@ export async function set (store, changes) {
|
||||
if (key === 'tags') {
|
||||
// Keep challenge and group tags
|
||||
const oldTags = user.tags.filter(t => {
|
||||
return t.group || t.challenge;
|
||||
return t.group;
|
||||
});
|
||||
|
||||
user.tags = changes[key].concat(oldTags);
|
||||
|
||||
@@ -22,12 +22,15 @@ let AUTH_SETTINGS = localStorage.getItem('habit-mobile-settings');
|
||||
|
||||
if (AUTH_SETTINGS) {
|
||||
AUTH_SETTINGS = JSON.parse(AUTH_SETTINGS);
|
||||
axios.defaults.headers.common['x-api-user'] = AUTH_SETTINGS.auth.apiId;
|
||||
axios.defaults.headers.common['x-api-key'] = AUTH_SETTINGS.auth.apiToken;
|
||||
|
||||
axios.defaults.headers.common['x-user-timezoneOffset'] = browserTimezoneOffset;
|
||||
if (AUTH_SETTINGS.auth && AUTH_SETTINGS.auth.apiId && AUTH_SETTINGS.auth.apiToken) {
|
||||
axios.defaults.headers.common['x-api-user'] = AUTH_SETTINGS.auth.apiId;
|
||||
axios.defaults.headers.common['x-api-key'] = AUTH_SETTINGS.auth.apiToken;
|
||||
|
||||
isUserLoggedIn = true;
|
||||
axios.defaults.headers.common['x-user-timezoneOffset'] = browserTimezoneOffset;
|
||||
|
||||
isUserLoggedIn = true;
|
||||
}
|
||||
}
|
||||
|
||||
const i18nData = window && window['habitica-i18n'];
|
||||
@@ -57,7 +60,7 @@ export default function () {
|
||||
isUserLoaded: false, // Means the user and the user's tasks are ready
|
||||
isAmazonReady: false, // Whether the Amazon Payments lib can be used
|
||||
user: asyncResourceFactory(),
|
||||
credentials: AUTH_SETTINGS ? {
|
||||
credentials: isUserLoggedIn ? {
|
||||
API_ID: AUTH_SETTINGS.auth.apiId,
|
||||
API_TOKEN: AUTH_SETTINGS.auth.apiToken,
|
||||
} : {},
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
} from '../../libs/email';
|
||||
import nconf from 'nconf';
|
||||
import get from 'lodash/get';
|
||||
import { model as Tag } from '../../models/tag';
|
||||
|
||||
const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL');
|
||||
const DELETE_CONFIRMATION = 'DELETE';
|
||||
@@ -305,7 +304,7 @@ api.updateUser = {
|
||||
|
||||
// Keep challenge and group tags
|
||||
user.tags.forEach(t => {
|
||||
if (t.group || t.challenge) {
|
||||
if (t.group) {
|
||||
oldTags.push(t);
|
||||
} else {
|
||||
removedTagsIds.push(t.id);
|
||||
@@ -320,7 +319,7 @@ api.updateUser = {
|
||||
removedTagsIds.splice(oldI, 1);
|
||||
}
|
||||
|
||||
user.tags.push(Tag.sanitize(t));
|
||||
user.tags.push(t);
|
||||
});
|
||||
|
||||
// Remove from all the tasks
|
||||
|
||||
Reference in New Issue
Block a user