Merge branch 'develop' into release
|
|
@ -7,7 +7,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|||
|
||||
// add hot-reload related code to entry chunks
|
||||
Object.keys(baseWebpackConfig.entry).forEach((name) => {
|
||||
baseWebpackConfig.entry[name] = ['./webpack/dev-client'].concat(baseWebpackConfig.entry[name]);
|
||||
baseWebpackConfig.entry[name] = baseWebpackConfig.entry[name].concat('./webpack/dev-client');
|
||||
});
|
||||
|
||||
module.exports = merge(baseWebpackConfig, {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ div
|
|||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.row {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #fff;
|
||||
font-size: 32px;
|
||||
|
|
@ -389,7 +393,14 @@ export default {
|
|||
}
|
||||
},
|
||||
async memberSelected (member) {
|
||||
this.$store.dispatch('user:castSpell', {key: this.selectedSpellToBuy.key, targetId: member.id});
|
||||
let castResult = await this.$store.dispatch('user:castSpell', {key: this.selectedSpellToBuy.key, targetId: member.id});
|
||||
|
||||
// Subtract gold for cards
|
||||
if (this.selectedSpellToBuy.pinType === 'card') {
|
||||
const newUserGp = castResult.data.data.user.stats.gp;
|
||||
this.$store.state.user.data.stats.gp = newUserGp;
|
||||
}
|
||||
|
||||
this.selectedSpellToBuy = null;
|
||||
|
||||
if (this.user.party._id) {
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 366 KiB After Width: | Height: | Size: 363 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 262 KiB |
|
|
@ -223,6 +223,7 @@
|
|||
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
|
|
|||
3
website/client/assets/svg/broken-megaphone.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="12" viewBox="0 0 14 12">
|
||||
<path fill-rule="evenodd" d="M2 7.05V3.954l4.873 2.872-4.291.715A.5.5 0 0 1 2 7.05zm8-1.108L3.83 2.306 10 3.334v2.608zm2.329-4.248l-.024-.004c-.007-.002-.012-.007-.02-.009-.017-.005-.035.001-.052-.003L2.329.028A2 2 0 0 0 0 2v5.64a2 2 0 0 0 2.329 1.972l7.056-1.176-.525 2.1a1.175 1.175 0 0 0 2.28.57l.772-3.09.417-.07A2 2 0 0 0 14 5.971V3.667a2 2 0 0 0-1.671-1.973z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 463 B |
|
|
@ -69,8 +69,8 @@
|
|||
a.social-circle(href='https://www.facebook.com/Habitica', target='_blank')
|
||||
.social-icon.facebook.svg-icon(v-html='icons.facebook')
|
||||
.row
|
||||
.col-10 {{ $t('donateText3') }}
|
||||
.col-2
|
||||
.col-12.col-md-10 {{ $t('donateText3') }}
|
||||
.col-12.col-md-2
|
||||
button.btn.btn-contribute(@click="donate()", v-if="user")
|
||||
.svg-icon.heart(v-html="icons.heart")
|
||||
.text {{ $t('companyDonate') }}
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
.col-12
|
||||
hr
|
||||
.row
|
||||
.col-5
|
||||
.col-12.col-md-5
|
||||
| © 2017 Habitica. All rights reserved.
|
||||
.debug.float-left(v-if="!IS_PRODUCTION && isUserLoaded")
|
||||
button.btn.btn-primary(@click="debugMenuShown = !debugMenuShown") Toggle Debug Menu
|
||||
|
|
@ -102,9 +102,9 @@
|
|||
a.btn.btn-default(@click="addQuestProgress()", tooltip="+1000 to boss quests. 300 items to collection quests") Quest Progress Up
|
||||
a.btn.btn-default(@click="makeAdmin()") Make Admin
|
||||
a.btn.btn-default(@click="openModifyInventoryModal()") Modify Inventory
|
||||
.col-2.text-center
|
||||
.col-12.col-md-2.text-center
|
||||
.logo.svg-icon(v-html='icons.gryphon')
|
||||
.col-5.text-right
|
||||
.col-12.col-md-5.text-right
|
||||
template(v-if="!isExpandedFooter")
|
||||
span
|
||||
a(:href="getDataDisplayToolUrl", target='_blank') {{ $t('dataDisplayTool') }}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
.seamless_stars_varied_opacity_repeat
|
||||
|
||||
form#login-form(
|
||||
@submit.prevent='handleSubmit',
|
||||
@keyup.enter="handleSubmit",
|
||||
@submit.prevent='handleSubmit',
|
||||
@keyup.enter="handleSubmit",
|
||||
v-if="!forgotPassword && !resetPasswordSetNewOne",
|
||||
)
|
||||
.text-center
|
||||
|
|
@ -14,11 +14,11 @@
|
|||
div
|
||||
.svg-icon.habitica-logo(v-html="icons.habiticaIcon")
|
||||
.form-group.row.text-center
|
||||
.col-6
|
||||
.col-12.col-md-6
|
||||
.btn.btn-secondary.social-button(@click='socialAuth("facebook")')
|
||||
.svg-icon.social-icon(v-html="icons.facebookIcon")
|
||||
.text {{registering ? $t('signUpWithSocial', {social: 'Facebook'}) : $t('loginWithSocial', {social: 'Facebook'})}}
|
||||
.col-6
|
||||
.col-12.col-md-6
|
||||
.btn.btn-secondary.social-button(@click='socialAuth("google")')
|
||||
.svg-icon.social-icon(v-html="icons.googleIcon")
|
||||
span {{registering ? $t('signUpWithSocial', {social: 'Google'}) : $t('loginWithSocial', {social: 'Google'})}}
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
input#confirmPasswordInput.form-control(type='password', :placeholder='$t("confirmPasswordPlaceholder")', v-model='passwordConfirm')
|
||||
.text-center
|
||||
.btn.btn-info(
|
||||
@click='resetPasswordSetNewOneLink()',
|
||||
@click='resetPasswordSetNewOneLink()',
|
||||
:enabled="!resetPasswordSetNewOneData.hasError"
|
||||
) {{$t('setNewPass')}}
|
||||
|
||||
|
|
@ -121,6 +121,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
#login-form {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
background-color: $purple-200;
|
||||
background: $purple-200; /* For browsers that do not support gradients */
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@
|
|||
leave-challenge-modal(:challengeId='challenge._id')
|
||||
close-challenge-modal(:members='members', :challengeId='challenge._id')
|
||||
challenge-member-progress-modal(:memberId='progressMemberId', :challengeId='challenge._id')
|
||||
|
||||
.col-8.standard-page
|
||||
.col-12.col-md-8.standard-page
|
||||
.row
|
||||
.col-8
|
||||
.col-12.col-md-8
|
||||
h1(v-markdown='challenge.name')
|
||||
div
|
||||
strong(v-once) {{$t('createdBy')}}:
|
||||
|
|
@ -20,7 +19,7 @@
|
|||
// span {{challenge.endDate}}
|
||||
.tags
|
||||
span.tag(v-for='tag in challenge.tags') {{tag}}
|
||||
.col-4
|
||||
.col-12.col-md-4
|
||||
.box(@click="showMemberModal()")
|
||||
.svg-icon.member-icon(v-html="icons.memberIcon")
|
||||
| {{challenge.memberCount}}
|
||||
|
|
@ -30,7 +29,7 @@
|
|||
| {{challenge.prize}}
|
||||
.details(v-once) {{$t('prize')}}
|
||||
.row.challenge-actions
|
||||
.col-7.offset-5
|
||||
.col-12.col-md-7.offset-md-5
|
||||
span.view-progress
|
||||
strong {{ $t('viewProgressOf') }}
|
||||
member-search-dropdown(:text="$t('selectParticipant')", :members='members', :challengeId='challengeId', @member-selected='openMemberProgressModal')
|
||||
|
|
@ -56,7 +55,7 @@
|
|||
:taskListOverride='tasksByType[column]',
|
||||
v-on:editTask="editTask",
|
||||
v-if='tasksByType[column].length > 0')
|
||||
.col-4.sidebar.standard-page
|
||||
.col-12.col-md-4.sidebar.standard-page
|
||||
.acitons
|
||||
div(v-if='canJoin')
|
||||
button.btn.btn-success(v-once, @click='joinChallenge()') {{$t('joinChallenge')}}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
.row
|
||||
challenge-modal(v-on:createChallenge='challengeCreated')
|
||||
sidebar(v-on:search="updateSearch", v-on:filter="updateFilters")
|
||||
|
||||
.col-10.standard-page
|
||||
.col-12.col-md-10.standard-page
|
||||
.row.header-row
|
||||
.col-md-8.text-left
|
||||
h1(v-once) {{$t('findChallenges')}}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
.standard-sidebar.d-none.d-sm-block
|
||||
.form-group
|
||||
input.form-control.search(type="text", :placeholder="$t('search')", v-model='searchTerm')
|
||||
|
||||
form
|
||||
h2(v-once) {{ $t('filter') }}
|
||||
.form-group
|
||||
|
|
|
|||
|
|
@ -57,10 +57,19 @@ export default {
|
|||
this.currentSearchPosition = newText.length - 1;
|
||||
},
|
||||
chat () {
|
||||
this.resetDefaults();
|
||||
this.grabUserNames();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resetDefaults () {
|
||||
// Mounted is not called when switching between group pages because they have the
|
||||
// the same parent component. So, reset the data
|
||||
this.currentSearch = '';
|
||||
this.searchActive = false;
|
||||
this.currentSearchPosition = 0;
|
||||
this.tmpSelections = [];
|
||||
},
|
||||
grabUserNames () {
|
||||
let usersThatMessage = groupBy(this.chat, 'user');
|
||||
for (let userName in usersThatMessage) {
|
||||
|
|
|
|||
276
website/client/components/chat/chatCard.vue
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
<template lang="pug">
|
||||
div
|
||||
.mentioned-icon(v-if='isUserMentioned')
|
||||
.message-hidden(v-if='msg.flagCount === 1 && user.contributor.admin') Message flagged once, not hidden
|
||||
.message-hidden(v-if='msg.flagCount > 1 && user.contributor.admin') Message hidden
|
||||
.card-body
|
||||
h3.leader(
|
||||
:class='userLevelStyle(msg)',
|
||||
@click="showMemberModal(msg.uuid)",
|
||||
v-b-tooltip.hover.top="('contributor' in msg) ? msg.contributor.text : ''",
|
||||
)
|
||||
| {{msg.user}}
|
||||
.svg-icon(v-html="tierIcon", v-if='showShowTierStyle')
|
||||
p.time {{msg.timestamp | timeAgo}}
|
||||
.text(v-markdown='msg.text')
|
||||
hr
|
||||
.action(@click='like()', v-if='msg.likes', :class='{active: msg.likes[user._id]}')
|
||||
.svg-icon(v-html="icons.like")
|
||||
span(v-if='!msg.likes[user._id]') {{ $t('like') }}
|
||||
span(v-if='msg.likes[user._id]') {{ $t('liked') }}
|
||||
// @TODO make copyAsTodo work in Tavern, guilds, party (inbox can be done later)
|
||||
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='!inbox && user.flags.communityGuidelinesAccepted && msg.uuid !== "system"', @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 || inbox || user.contributor.admin', @click='remove()')
|
||||
.svg-icon(v-html="icons.delete")
|
||||
| {{$t('delete')}}
|
||||
span.action.float-right.liked(v-if='likeCount > 0')
|
||||
.svg-icon(v-html="icons.liked")
|
||||
| + {{ likeCount }}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/tiers.scss';
|
||||
|
||||
.mentioned-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: #bda8ff;
|
||||
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
|
||||
position: absolute;
|
||||
right: -.5em;
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
.message-hidden {
|
||||
margin-left: 1.5em;
|
||||
margin-top: 1em;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
.leader {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h3 { // this is the user name
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
display: inline-block;
|
||||
margin-left: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 12px;
|
||||
color: #878190;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 14px;
|
||||
color: #4e4a57;
|
||||
text-align: left !important;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
display: inline-block;
|
||||
color: #878190;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.action:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.liked:hover {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.action .svg-icon {
|
||||
margin-right: .2em;
|
||||
width: 16px;
|
||||
display: inline-block;
|
||||
color: #A5A1AC;
|
||||
}
|
||||
|
||||
.action.active, .active .svg-icon {
|
||||
color: #46a7d9
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import escapeRegExp from 'lodash/escapeRegExp';
|
||||
|
||||
import markdownDirective from 'client/directives/markdown';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import styleHelper from 'client/mixins/styleHelper';
|
||||
|
||||
import deleteIcon from 'assets/svg/delete.svg';
|
||||
import copyIcon from 'assets/svg/copy.svg';
|
||||
import likeIcon from 'assets/svg/like.svg';
|
||||
import likedIcon from 'assets/svg/liked.svg';
|
||||
import reportIcon from 'assets/svg/report.svg';
|
||||
import tier1 from 'assets/svg/tier-1.svg';
|
||||
import tier2 from 'assets/svg/tier-2.svg';
|
||||
import tier3 from 'assets/svg/tier-3.svg';
|
||||
import tier4 from 'assets/svg/tier-4.svg';
|
||||
import tier5 from 'assets/svg/tier-5.svg';
|
||||
import tier6 from 'assets/svg/tier-6.svg';
|
||||
import tier7 from 'assets/svg/tier-7.svg';
|
||||
import tier8 from 'assets/svg/tier-mod.svg';
|
||||
import tier9 from 'assets/svg/tier-staff.svg';
|
||||
import tierNPC from 'assets/svg/tier-npc.svg';
|
||||
|
||||
export default {
|
||||
props: ['msg', 'inbox', 'groupId'],
|
||||
mixins: [styleHelper],
|
||||
data () {
|
||||
return {
|
||||
copyingMessage: {},
|
||||
icons: Object.freeze({
|
||||
like: likeIcon,
|
||||
copy: copyIcon,
|
||||
report: reportIcon,
|
||||
delete: deleteIcon,
|
||||
liked: likedIcon,
|
||||
tier1,
|
||||
tier2,
|
||||
tier3,
|
||||
tier4,
|
||||
tier5,
|
||||
tier6,
|
||||
tier7,
|
||||
tier8,
|
||||
tier9,
|
||||
tierNPC,
|
||||
}),
|
||||
};
|
||||
},
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
filters: {
|
||||
timeAgo (value) {
|
||||
return moment(value).fromNow();
|
||||
},
|
||||
date (value) {
|
||||
// @TODO: Add user preference
|
||||
return moment(value).toDate();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
isUserMentioned () {
|
||||
const message = this.msg;
|
||||
let user = this.user;
|
||||
|
||||
if (message.hasOwnProperty('highlight')) return message.highlight;
|
||||
|
||||
message.highlight = false;
|
||||
let messagetext = message.text.toLowerCase();
|
||||
let username = user.profile.name;
|
||||
let mentioned = messagetext.indexOf(username.toLowerCase());
|
||||
let escapedUsername = escapeRegExp(username);
|
||||
let pattern = `@${escapedUsername}([^\w]|$){1}`;
|
||||
|
||||
if (mentioned === -1) return message.highlight;
|
||||
|
||||
let preceedingchar = messagetext.substring(mentioned - 1, mentioned);
|
||||
if (mentioned === 0 || preceedingchar.trim() === '' || preceedingchar === '@') {
|
||||
let regex = new RegExp(pattern, 'i');
|
||||
message.highlight = regex.test(messagetext);
|
||||
}
|
||||
|
||||
return message.highlight;
|
||||
},
|
||||
likeCount () {
|
||||
const message = this.msg;
|
||||
if (!message.likes) return 0;
|
||||
|
||||
let likeCount = 0;
|
||||
for (let key in message.likes) {
|
||||
let like = message.likes[key];
|
||||
if (like) likeCount += 1;
|
||||
}
|
||||
return likeCount;
|
||||
},
|
||||
showShowTierStyle () {
|
||||
const message = this.msg;
|
||||
const isContributor = Boolean(message.contributor && message.contributor.level);
|
||||
const isNPC = Boolean(message.backer && message.backer.npc);
|
||||
return isContributor || isNPC;
|
||||
},
|
||||
tierIcon () {
|
||||
const message = this.msg;
|
||||
const isNPC = Boolean(message.backer && message.backer.npc);
|
||||
if (isNPC) {
|
||||
return this.icons.tierNPC;
|
||||
}
|
||||
return this.icons[`tier${message.contributor.level}`];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async like () {
|
||||
let message = cloneDeep(this.msg);
|
||||
|
||||
await this.$store.dispatch('chat:like', {
|
||||
groupId: this.groupId,
|
||||
chatId: message.id,
|
||||
});
|
||||
|
||||
if (!message.likes[this.user._id]) {
|
||||
message.likes[this.user._id] = true;
|
||||
} else {
|
||||
message.likes[this.user._id] = !message.likes[this.user._id];
|
||||
}
|
||||
|
||||
this.$emit('messaged-liked', message);
|
||||
},
|
||||
copyAsTodo (message) {
|
||||
// @TODO: Move to Habitica Event
|
||||
this.copyingMessage = message;
|
||||
this.$root.$emit('bv::show::modal', 'copyAsTodo');
|
||||
},
|
||||
async report () {
|
||||
this.$root.$emit('habitica::report-chat', {
|
||||
message: this.msg,
|
||||
groupId: this.groupId,
|
||||
});
|
||||
},
|
||||
async remove () {
|
||||
if (!confirm(this.$t('areYouSureDeleteMessage'))) return;
|
||||
|
||||
const message = this.msg;
|
||||
this.$emit('message-removed', message);
|
||||
|
||||
if (this.inbox) {
|
||||
axios.delete(`/api/v3/user/messages/${message.id}`);
|
||||
this.$delete(this.user.inbox.messages, message.id);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$store.dispatch('chat:deleteChat', {
|
||||
groupId: this.groupId,
|
||||
chatId: message.id,
|
||||
});
|
||||
},
|
||||
showMemberModal (memberId) {
|
||||
this.$emit('show-member-modal', memberId);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -2,9 +2,8 @@
|
|||
.container
|
||||
.row
|
||||
.col-12
|
||||
copy-as-todo-modal(:copying-message='copyingMessage', :group-name='groupName', :group-id='groupId')
|
||||
copy-as-todo-modal(:group-name='groupName', :group-id='groupId')
|
||||
report-flag-modal
|
||||
|
||||
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)')
|
||||
|
|
@ -19,78 +18,22 @@
|
|||
@click.native="showMemberModal(msg.uuid)",
|
||||
)
|
||||
.card(:class='inbox ? "col-8" : "col-10"')
|
||||
.mentioned-icon(v-if='isUserMentioned(msg)')
|
||||
.message-hidden(v-if='msg.flagCount === 1 && user.contributor.admin') Message flagged once, not hidden
|
||||
.message-hidden(v-if='msg.flagCount > 1 && user.contributor.admin') Message hidden
|
||||
.card-body
|
||||
h3.leader(
|
||||
:class='userLevelStyle(msg)',
|
||||
@click="showMemberModal(msg.uuid)",
|
||||
v-b-tooltip.hover.top="('contributor' in msg) ? msg.contributor.text : ''",
|
||||
)
|
||||
| {{msg.user}}
|
||||
.svg-icon(v-html="getTierIcon(msg)", v-if='showShowTierStyle(msg)')
|
||||
p.time {{msg.timestamp | timeAgo}}
|
||||
.text(v-markdown='msg.text')
|
||||
hr
|
||||
.action(@click='like(msg, index)', v-if='msg.likes', :class='{active: msg.likes[user._id]}')
|
||||
.svg-icon(v-html="icons.like")
|
||||
span(v-if='!msg.likes[user._id]') {{ $t('like') }}
|
||||
span(v-if='msg.likes[user._id]') {{ $t('liked') }}
|
||||
// @TODO make copyAsTodo work in Tavern, guilds, party (inbox can be done later)
|
||||
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='!inbox && user.flags.communityGuidelinesAccepted && msg.uuid !== "system"', @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 || 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')
|
||||
.svg-icon(v-html="icons.liked")
|
||||
| + {{ likeCount(msg) }}
|
||||
// @TODO can we avoid duplicating all this code? Cannot we just push everything
|
||||
// to the right if the user is the author?
|
||||
// Maybe we just create two sub components instead
|
||||
chat-card(
|
||||
:msg='msg',
|
||||
:inbox='inbox',
|
||||
:groupId='groupId',
|
||||
@messaged-liked='messageLiked',
|
||||
@message-removed='messageRemoved',
|
||||
@show-member-modal='showMemberModal')
|
||||
.row(v-if='user._id === msg.uuid')
|
||||
.card(:class='inbox ? "col-8" : "col-10"')
|
||||
.mentioned-icon(v-if='isUserMentioned(msg)')
|
||||
.message-hidden(v-if='msg.flagCount === 1 && user.contributor.admin') Message flagged once, not hidden
|
||||
.message-hidden(v-if='msg.flagCount > 1 && user.contributor.admin') Message hidden
|
||||
.card-body
|
||||
h3.leader(
|
||||
:class='userLevelStyle(msg)',
|
||||
@click="showMemberModal(msg.uuid)",
|
||||
v-b-tooltip.hover.top="('contributor' in msg) ? msg.contributor.text : ''",
|
||||
)
|
||||
| {{msg.user}}
|
||||
.svg-icon(v-html="getTierIcon(msg)", v-if='showShowTierStyle(msg)')
|
||||
p.time {{msg.timestamp | timeAgo}}
|
||||
.text(v-markdown='msg.text')
|
||||
hr
|
||||
.action(@click='like(msg, index)', v-if='msg.likes', :class='{active: msg.likes[user._id]}')
|
||||
.svg-icon(v-html="icons.like")
|
||||
span(v-if='!msg.likes[user._id]') {{ $t('like') }}
|
||||
span(v-if='msg.likes[user._id]') {{ $t('liked') }}
|
||||
// @TODO make copyAsTodo work in Tavern, guilds, party (inbox can be done later)
|
||||
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')}}
|
||||
span.action.float-right.liked(v-if='likeCount(msg) > 0')
|
||||
.svg-icon(v-html="icons.liked")
|
||||
| + {{ likeCount(msg) }}
|
||||
chat-card(
|
||||
:msg='msg',
|
||||
:inbox='inbox',
|
||||
:groupId='groupId',
|
||||
@messaged-liked='messageLiked',
|
||||
@message-removed='messageRemoved',
|
||||
@show-member-modal='showMemberModal')
|
||||
div(:class='inbox ? "col-4" : "col-2"')
|
||||
avatar(
|
||||
v-if='cachedProfileData[msg.uuid] && !cachedProfileData[msg.uuid].rejected',
|
||||
|
|
@ -103,38 +46,6 @@
|
|||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
@import '~client/assets/scss/tiers.scss';
|
||||
|
||||
.leader {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 12px;
|
||||
color: #878190;
|
||||
}
|
||||
|
||||
.mentioned-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: #bda8ff;
|
||||
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
|
||||
position: absolute;
|
||||
right: -.5em;
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
h3 { // this is the user name
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
display: inline-block;
|
||||
margin-left: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.hr {
|
||||
width: 100%;
|
||||
|
|
@ -161,86 +72,28 @@
|
|||
.card {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 14px;
|
||||
color: #4e4a57;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: inline-block;
|
||||
color: #878190;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.action:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.liked:hover {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.action .svg-icon {
|
||||
margin-right: .2em;
|
||||
width: 16px;
|
||||
display: inline-block;
|
||||
color: #A5A1AC;
|
||||
}
|
||||
|
||||
.action.active, .active .svg-icon {
|
||||
color: #46a7d9
|
||||
}
|
||||
|
||||
.message-hidden {
|
||||
margin-left: 1.5em;
|
||||
margin-top: 1em;
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import axios from 'axios';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import debounce from 'lodash/debounce';
|
||||
import escapeRegExp from 'lodash/escapeRegExp';
|
||||
import markdownDirective from 'client/directives/markdown';
|
||||
import Avatar from '../avatar';
|
||||
import styleHelper from 'client/mixins/styleHelper';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
|
||||
import Avatar from '../avatar';
|
||||
import copyAsTodoModal from './copyAsTodoModal';
|
||||
import reportFlagModal from './reportFlagModal';
|
||||
|
||||
import deleteIcon from 'assets/svg/delete.svg';
|
||||
import copyIcon from 'assets/svg/copy.svg';
|
||||
import likeIcon from 'assets/svg/like.svg';
|
||||
import likedIcon from 'assets/svg/liked.svg';
|
||||
import reportIcon from 'assets/svg/report.svg';
|
||||
import tier1 from 'assets/svg/tier-1.svg';
|
||||
import tier2 from 'assets/svg/tier-2.svg';
|
||||
import tier3 from 'assets/svg/tier-3.svg';
|
||||
import tier4 from 'assets/svg/tier-4.svg';
|
||||
import tier5 from 'assets/svg/tier-5.svg';
|
||||
import tier6 from 'assets/svg/tier-6.svg';
|
||||
import tier7 from 'assets/svg/tier-7.svg';
|
||||
import tier8 from 'assets/svg/tier-mod.svg';
|
||||
import tier9 from 'assets/svg/tier-staff.svg';
|
||||
import tierNPC from 'assets/svg/tier-npc.svg';
|
||||
import chatCard from './chatCard';
|
||||
|
||||
export default {
|
||||
props: ['chat', 'groupId', 'groupName', 'inbox'],
|
||||
mixins: [styleHelper],
|
||||
components: {
|
||||
copyAsTodoModal,
|
||||
reportFlagModal,
|
||||
chatCard,
|
||||
Avatar,
|
||||
},
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
mounted () {
|
||||
this.loadProfileCache();
|
||||
},
|
||||
|
|
@ -252,24 +105,6 @@ export default {
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
like: likeIcon,
|
||||
copy: copyIcon,
|
||||
report: reportIcon,
|
||||
delete: deleteIcon,
|
||||
liked: likedIcon,
|
||||
tier1,
|
||||
tier2,
|
||||
tier3,
|
||||
tier4,
|
||||
tier5,
|
||||
tier6,
|
||||
tier7,
|
||||
tier8,
|
||||
tier9,
|
||||
tierNPC,
|
||||
}),
|
||||
copyingMessage: {},
|
||||
currentDayDividerDisplay: moment().day(),
|
||||
cachedProfileData: {},
|
||||
currentProfileLoadedCount: 0,
|
||||
|
|
@ -277,15 +112,6 @@ export default {
|
|||
loading: false,
|
||||
};
|
||||
},
|
||||
filters: {
|
||||
timeAgo (value) {
|
||||
return moment(value).fromNow();
|
||||
},
|
||||
date (value) {
|
||||
// @TODO: Add user preference
|
||||
return moment(value).toDate();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
// @TODO: We need a different lazy load mechnism.
|
||||
|
|
@ -304,28 +130,6 @@ export default {
|
|||
handleScroll () {
|
||||
this.loadProfileCache(window.scrollY / 1000);
|
||||
},
|
||||
isUserMentioned (message) {
|
||||
let user = this.user;
|
||||
|
||||
if (message.hasOwnProperty('highlight')) return message.highlight;
|
||||
|
||||
message.highlight = false;
|
||||
let messagetext = message.text.toLowerCase();
|
||||
let username = user.profile.name;
|
||||
let mentioned = messagetext.indexOf(username.toLowerCase());
|
||||
let escapedUsername = escapeRegExp(username);
|
||||
let pattern = `@${escapedUsername}([^\w]|$){1}`;
|
||||
|
||||
if (mentioned === -1) return message.highlight;
|
||||
|
||||
let preceedingchar = messagetext.substring(mentioned - 1, mentioned);
|
||||
if (mentioned === 0 || preceedingchar.trim() === '' || preceedingchar === '@') {
|
||||
let regex = new RegExp(pattern, 'i');
|
||||
message.highlight = regex.test(messagetext);
|
||||
}
|
||||
|
||||
return message.highlight;
|
||||
},
|
||||
canViewFlag (message) {
|
||||
if (message.uuid === this.user._id) return true;
|
||||
if (!message.flagCount || message.flagCount < 2) return true;
|
||||
|
|
@ -381,58 +185,6 @@ export default {
|
|||
|
||||
return false;
|
||||
},
|
||||
likeCount (message) {
|
||||
if (!message.likes) return 0;
|
||||
|
||||
let likeCount = 0;
|
||||
for (let key in message.likes) {
|
||||
let like = message.likes[key];
|
||||
if (like) likeCount += 1;
|
||||
}
|
||||
return likeCount;
|
||||
},
|
||||
async like (messageToLike, index) {
|
||||
let message = cloneDeep(messageToLike);
|
||||
|
||||
await this.$store.dispatch('chat:like', {
|
||||
groupId: this.groupId,
|
||||
chatId: message.id,
|
||||
});
|
||||
|
||||
if (!message.likes[this.user._id]) {
|
||||
message.likes[this.user._id] = true;
|
||||
} else {
|
||||
message.likes[this.user._id] = !message.likes[this.user._id];
|
||||
}
|
||||
|
||||
this.chat.splice(index, 1, message);
|
||||
},
|
||||
copyAsTodo (message) {
|
||||
this.copyingMessage = message;
|
||||
this.$root.$emit('bv::show::modal', 'copyAsTodo');
|
||||
},
|
||||
async report (message) {
|
||||
this.$store.state.flagChatOptions.message = message;
|
||||
this.$store.state.flagChatOptions.groupId = this.groupId;
|
||||
|
||||
this.$root.$emit('bv::show::modal', 'report-flag');
|
||||
},
|
||||
async remove (message, index) {
|
||||
if (!confirm(this.$t('areYouSureDeleteMessage'))) return;
|
||||
|
||||
this.chat.splice(index, 1);
|
||||
|
||||
if (this.inbox) {
|
||||
axios.delete(`/api/v3/user/messages/${message.id}`);
|
||||
this.$delete(this.user.inbox.messages, message.id);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$store.dispatch('chat:deleteChat', {
|
||||
groupId: this.groupId,
|
||||
chatId: message.id,
|
||||
});
|
||||
},
|
||||
showMemberModal (memberId) {
|
||||
const profile = this.cachedProfileData[memberId];
|
||||
|
||||
|
|
@ -444,17 +196,17 @@ export default {
|
|||
});
|
||||
}
|
||||
},
|
||||
showShowTierStyle (message) {
|
||||
const isContributor = Boolean(message.contributor && message.contributor.level);
|
||||
const isNPC = Boolean(message.backer && message.backer.npc);
|
||||
return isContributor || isNPC;
|
||||
messageLiked (message) {
|
||||
const chatIndex = findIndex(this.chat, chatMessage => {
|
||||
return chatMessage.id === message.id;
|
||||
});
|
||||
this.chat.splice(chatIndex, 1, message);
|
||||
},
|
||||
getTierIcon (message) {
|
||||
const isNPC = Boolean(message.backer && message.backer.npc);
|
||||
if (isNPC) {
|
||||
return this.icons.tierNPC;
|
||||
}
|
||||
return this.icons[`tier${message.contributor.level}`];
|
||||
messageRemoved (message) {
|
||||
const chatIndex = findIndex(this.chat, chatMessage => {
|
||||
return chatMessage.id === message.id;
|
||||
});
|
||||
this.chat.splice(chatIndex, 1);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,12 +33,6 @@ export default {
|
|||
name: `<span class='text-danger'>${reportMessage}</span>`,
|
||||
};
|
||||
},
|
||||
abuseObject () {
|
||||
return this.$store.state.flagChatOptions.message;
|
||||
},
|
||||
groupId () {
|
||||
return this.$store.state.flagChatOptions.groupId;
|
||||
},
|
||||
},
|
||||
data () {
|
||||
let abuseFlagModalBody = {
|
||||
|
|
@ -49,8 +43,21 @@ export default {
|
|||
|
||||
return {
|
||||
abuseFlagModalBody,
|
||||
abuseObject: '',
|
||||
groupId: '',
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.$root.$on('habitica::report-chat', data => {
|
||||
if (!data.message || !data.groupId) return;
|
||||
this.abuseObject = data.message;
|
||||
this.groupId = data.groupId;
|
||||
this.$root.$emit('bv::show::modal', 'report-flag');
|
||||
});
|
||||
},
|
||||
destroyed () {
|
||||
this.$root.$off('habitica::report-chat');
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'report-flag');
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ b-modal#create-party-modal(title="Empty", size='lg', hide-footer=true)
|
|||
.header-wrap {
|
||||
padding: 0;
|
||||
color: #4e4a57;
|
||||
width: 100%;
|
||||
|
||||
.quest_screen {
|
||||
background-image: url('~client/assets/images/quest_screen.png');
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@
|
|||
group-gems-modal
|
||||
.col-12.col-sm-8.standard-page
|
||||
.row
|
||||
.col-6.title-details
|
||||
.col-12.col-md-6.title-details
|
||||
h1 {{group.name}}
|
||||
strong.float-left(v-once) {{$t('groupLeader')}}
|
||||
span.leader.float-left(v-if='group.leader.profile', @click='showMemberProfile(group.leader)') : {{group.leader.profile.name}}
|
||||
.col-6
|
||||
.col-12.col-md-6
|
||||
.row.icon-row
|
||||
.col-4.offset-4(v-bind:class="{ 'offset-8': isParty }")
|
||||
.item-with-icon(@click="showMemberModal()")
|
||||
|
|
@ -146,6 +146,7 @@
|
|||
|
||||
.svg-icon.shield, .svg-icon.gem {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 0 auto;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
|
|
@ -438,6 +439,14 @@ export default {
|
|||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.$set(this, 'searchId', to.params.groupId);
|
||||
|
||||
// Reset chat
|
||||
this.newMessage = '';
|
||||
this.coords = {
|
||||
TOP: 0,
|
||||
LEFT: 0,
|
||||
};
|
||||
|
||||
next();
|
||||
},
|
||||
watch: {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.no-guilds-wrapper {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ div
|
|||
.view-party.d-flex.align-items-center(
|
||||
v-if="user.party && user.party._id && partyMembers && partyMembers.length > 1",
|
||||
)
|
||||
button.btn.btn-primary(@click='openPartyModal()') {{ $t('viewParty') }}
|
||||
button.btn.btn-primary.view-party-button(@click='openPartyModal()') {{ $t('viewParty') }}
|
||||
.party-members.d-flex(
|
||||
v-if="partyMembers && partyMembers.length > 1",
|
||||
v-resize="1500",
|
||||
|
|
@ -98,6 +98,12 @@ div
|
|||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.view-party-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
searchText: throttle(function throttleSearch () {
|
||||
this.searchTextThrottled = this.searchText;
|
||||
this.searchTextThrottled = this.searchText.toLowerCase();
|
||||
}, 250),
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
searchText: throttle(function throttleSearch () {
|
||||
this.searchTextThrottled = this.searchText;
|
||||
this.searchTextThrottled = this.searchText.toLowerCase();
|
||||
}, 250),
|
||||
},
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -270,9 +270,6 @@ export default {
|
|||
if (this.user.needsCron) return;
|
||||
this.handleUserNotifications(after);
|
||||
},
|
||||
userAchievements () {
|
||||
this.playSound('Achievement_Unlocked');
|
||||
},
|
||||
armoireEmpty (after, before) {
|
||||
if (after === before || after === false) return;
|
||||
this.$root.$emit('bv::show::modal', 'armoire-empty');
|
||||
|
|
@ -280,6 +277,7 @@ export default {
|
|||
questCompleted () {
|
||||
if (!this.questCompleted) return;
|
||||
this.$root.$emit('bv::show::modal', 'quest-completed');
|
||||
this.playSound('Achievement_Unlocked');
|
||||
},
|
||||
invitedToQuest (after) {
|
||||
if (after !== true) return;
|
||||
|
|
@ -326,10 +324,12 @@ export default {
|
|||
|
||||
if (this.questCompleted) {
|
||||
this.$root.$emit('bv::show::modal', 'quest-completed');
|
||||
this.playSound('Achievement_Unlocked');
|
||||
}
|
||||
|
||||
if (this.userClassSelect) {
|
||||
this.$root.$emit('bv::show::modal', 'choose-class');
|
||||
this.playSound('Achievement_Unlocked');
|
||||
}
|
||||
},
|
||||
showLevelUpNotifications (newlevel) {
|
||||
|
|
|
|||
|
|
@ -61,10 +61,10 @@
|
|||
h1.mb-4.page-header(v-once) {{ $t('market') }}
|
||||
|
||||
.clearfix(v-if="viewOptions['equipment'].selected")
|
||||
h2.float-left.mb-3
|
||||
h2.float-left.mb-3.filters-title
|
||||
| {{ $t('equipment') }}
|
||||
|
||||
div.float-right
|
||||
.filters.float-right
|
||||
span.dropdown-label {{ $t('class') }}
|
||||
b-dropdown(right=true)
|
||||
span.dropdown-icon-item(slot="text")
|
||||
|
|
@ -328,6 +328,20 @@
|
|||
right: -.5em;
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.featuredItems .content {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.filters, .filters-title {
|
||||
float: none;
|
||||
button {
|
||||
margin-right: 4em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ transition(name="fade")
|
|||
|
||||
.svg-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template lang="pug">
|
||||
.container-fluid
|
||||
.row
|
||||
.col-md-6.offset-3
|
||||
.col-12.col-md-6.offset-md-3
|
||||
h1 {{ $t('frequentlyAskedQuestions') }}
|
||||
.faq-question(v-for='(heading, index) in headings')
|
||||
h2.accordion(@click='setActivePage(heading)') {{ $t(`faqQuestion${index}`) }}
|
||||
|
|
@ -14,6 +14,12 @@
|
|||
.faq-question {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.container-fluid {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
.pixel-horizontal.svg-icon(v-html='icons.pixelHorizontal')
|
||||
.container
|
||||
.row
|
||||
.col-12.col-sm-6.col-md-6.col-lg-6.offset-3.text-center
|
||||
.col-12.col-sm-6.col-md-6.col-lg-6.offset-sm-3.text-center
|
||||
h2 {{$t('gamifyYourLife')}}
|
||||
p.section-main {{$t('aboutHabitica')}}
|
||||
.row
|
||||
|
|
@ -502,6 +502,19 @@
|
|||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
#call-to-action .btn-primary {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
#call-to-action .featured .svg-icon {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ div(v-if='user.stats.lvl > 10')
|
|||
|
||||
.svg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: .2em;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@
|
|||
span.m-0(v-if="task.down") -{{task.counterDown}}
|
||||
.d-flex.align-items-center(v-if="task.challenge && task.challenge.id")
|
||||
.svg-icon.challenge(v-html="icons.challenge", v-if='!task.challenge.broken')
|
||||
.svg-icon.challenge.broken(v-html="icons.challenge", v-if='task.challenge.broken', @click='handleBrokenTask(task)')
|
||||
.svg-icon.challenge.broken(v-html="icons.brokenChallengeIcon", v-if='task.challenge.broken', @click='handleBrokenTask(task)')
|
||||
.d-flex.align-items-center(v-if="hasTags", :id="`tags-icon-${task._id}`")
|
||||
.svg-icon.tags(v-html="icons.tags")
|
||||
#tags-popover
|
||||
|
|
@ -500,6 +500,7 @@ import goldIcon from 'assets/svg/gold.svg';
|
|||
import streakIcon from 'assets/svg/streak.svg';
|
||||
import calendarIcon from 'assets/svg/calendar.svg';
|
||||
import challengeIcon from 'assets/svg/challenge.svg';
|
||||
import brokenChallengeIcon from 'assets/svg/broken-megaphone.svg';
|
||||
import tagsIcon from 'assets/svg/tags.svg';
|
||||
import checkIcon from 'assets/svg/check.svg';
|
||||
import editIcon from 'assets/svg/edit.svg';
|
||||
|
|
@ -534,6 +535,7 @@ export default {
|
|||
streak: streakIcon,
|
||||
calendar: calendarIcon,
|
||||
challenge: challengeIcon,
|
||||
brokenChallengeIcon,
|
||||
tags: tagsIcon,
|
||||
check: checkIcon,
|
||||
checklist: checklistIcon,
|
||||
|
|
|
|||
|
|
@ -492,8 +492,13 @@
|
|||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
margin-top: 50px;
|
||||
width: 100%;
|
||||
|
||||
|
||||
span {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.delete-task-btn {
|
||||
color: $red-50;
|
||||
|
|
@ -509,6 +514,12 @@
|
|||
width: 57px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.option-item {
|
||||
margin-right: 12px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
)
|
||||
.col-12
|
||||
.row.tasks-navigation
|
||||
.col-4.offset-4
|
||||
.col-12.col-md-4.offset-md-4
|
||||
.d-flex
|
||||
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
|
||||
button.btn.btn-secondary.dropdown-toggle.ml-2.d-flex.align-items-center(
|
||||
|
|
@ -288,6 +288,13 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.filter-panel {
|
||||
max-width: none;
|
||||
left: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template lang="pug">
|
||||
div
|
||||
b-modal#profile(title="Profile", size='lg', :hide-footer="true")
|
||||
div(slot='modal-header')
|
||||
.header(slot='modal-header')
|
||||
.profile-actions
|
||||
button.btn.btn-secondary.message-icon(@click='sendMessage()', v-b-tooltip.hover.left="$t('sendMessage')")
|
||||
.svg-icon.message-icon(v-html="icons.message")
|
||||
|
|
@ -17,22 +17,22 @@ div
|
|||
.col-12
|
||||
member-details(:member="user")
|
||||
.row
|
||||
.col-6.offset-3.text-center.nav
|
||||
.col-12.col-md-6.offset-md-3.text-center.nav
|
||||
.nav-item(@click='selectPage("profile")', :class="{active: selectedPage === 'profile'}") {{ $t('profile') }}
|
||||
.nav-item(@click='selectPage("stats")', :class="{active: selectedPage === 'stats'}") {{ $t('stats') }}
|
||||
.nav-item(@click='selectPage("achievements")', :class="{active: selectedPage === 'achievements'}") {{ $t('achievements') }}
|
||||
#userProfile.standard-page(v-show='selectedPage === "profile"', v-if='user.profile')
|
||||
.row
|
||||
.col-8
|
||||
.col-12.col-md-8
|
||||
.header
|
||||
h1 {{user.profile.name}}
|
||||
h4
|
||||
strong {{ $t('userId') }}:
|
||||
| {{user._id}}
|
||||
.col-4
|
||||
.col-12.col-md-4
|
||||
button.btn.btn-secondary(v-if='user._id === userLoggedIn._id', @click='editing = !editing') {{ $t('edit') }}
|
||||
.row(v-if='!editing')
|
||||
.col-8
|
||||
.col-12.col-md-8
|
||||
.about
|
||||
h2 {{ $t('about') }}
|
||||
p(v-if='user.profile.blurb', v-markdown='user.profile.blurb')
|
||||
|
|
@ -42,7 +42,7 @@ div
|
|||
img.img-rendering-auto(v-if='user.profile.imageUrl', :src='user.profile.imageUrl')
|
||||
p(v-else) {{ $t('noPhoto') }}
|
||||
|
||||
.col-4
|
||||
.col-12.col-md-4
|
||||
.info
|
||||
h2 {{ $t('info') }}
|
||||
div
|
||||
|
|
@ -93,7 +93,7 @@ div
|
|||
#achievements.standard-page.container(v-show='selectedPage === "achievements"', v-if='user.achievements')
|
||||
.row(v-for='(category, key) in achievements')
|
||||
h2.col-12.text-center {{ $t(key+'Achievs') }}
|
||||
.col-3.text-center(v-for='(achievement, key) in category.achievements')
|
||||
.col-12.col-md-3.text-center(v-for='(achievement, key) in category.achievements')
|
||||
.box.achievement-container(:id='key + "-achievement"', :class='{"achievement-unearned": !achievement.earned}')
|
||||
b-popover(
|
||||
:target="'#' + key + '-achievement'",
|
||||
|
|
@ -107,103 +107,103 @@ div
|
|||
.achievement.achievement-unearned(class='achievement-unearned2x', v-if='!achievement.earned')
|
||||
hr.col-12
|
||||
.row
|
||||
.col-6(v-if='user.achievements.challenges')
|
||||
.col-12.col-md-6(v-if='user.achievements.challenges')
|
||||
.achievement-icon.achievement-karaoke
|
||||
h2.text-center {{$t('challengesWon')}}
|
||||
div(v-for='chal in user.achievements.challenges')
|
||||
span(v-markdown='chal')
|
||||
hr
|
||||
.col-6(v-if='user.achievements.quests')
|
||||
.col-12.col-md-6(v-if='user.achievements.quests')
|
||||
.achievement-icon.achievement-alien
|
||||
h2.text-center {{$t('questsCompleted')}}
|
||||
div(v-for='(value, key) in user.achievements.quests')
|
||||
span {{ content.quests[key].text() }} ({{ value }})
|
||||
#stats.standard-page(v-show='selectedPage === "stats"', v-if='user.preferences')
|
||||
.row
|
||||
.col-6
|
||||
.col-12.col-md-6
|
||||
h2.text-center {{$t('equipment')}}
|
||||
.well
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: equippedItems.eyewear && equippedItems.eyewear.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${equippedItems.eyewear}`")
|
||||
h3 {{$t('eyewear')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: equippedItems.head && equippedItems.head.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${equippedItems.head}`")
|
||||
h3 {{$t('headgearCapitalized')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: equippedItems.headAccessory && equippedItems.headAccessory.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${equippedItems.headAccessory}`")
|
||||
h3 {{$t('headAccess')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: equippedItems.back && equippedItems.back.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${equippedItems.back}`")
|
||||
h3 {{$t('backAccess')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: equippedItems.armor && equippedItems.armor.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${equippedItems.armor}`")
|
||||
h3 {{$t('armorCapitalized')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: equippedItems.body && equippedItems.body.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${equippedItems.body}`")
|
||||
h3 {{$t('bodyAccess')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: equippedItems.weapon && equippedItems.weapon.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${equippedItems.weapon}`")
|
||||
h3 {{$t('mainHand')}}
|
||||
.col-4.item-wrapper
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: equippedItems.shield && equippedItems.shield.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${equippedItems.shield}`")
|
||||
h3 {{$t('offHand')}}
|
||||
.col-6
|
||||
.col-12.col-md-6
|
||||
h2.text-center {{$t('costume')}}
|
||||
.well
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: costumeItems.eyewear && costumeItems.eyewear.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${costumeItems.eyewear}`")
|
||||
h3 {{$t('eyewear')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: costumeItems.head && costumeItems.head.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${costumeItems.head}`")
|
||||
h3 {{$t('headgearCapitalized')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: costumeItems.headAccessory && costumeItems.headAccessory.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${costumeItems.headAccessory}`")
|
||||
h3 {{$t('headAccess')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: costumeItems.back && costumeItems.back.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${costumeItems.back}`")
|
||||
h3 {{$t('backAccess')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: costumeItems.armor && costumeItems.armor.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${costumeItems.armor}`")
|
||||
h3 {{$t('armorCapitalized')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: costumeItems.body && costumeItems.body.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${costumeItems.body}`")
|
||||
h3 {{$t('bodyAccess')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: costumeItems.weapon && costumeItems.weapon.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${costumeItems.weapon}`")
|
||||
h3 {{$t('mainHand')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: user.preferences.background}', style="overflow:hidden")
|
||||
div(:class="'icon_background_' + user.preferences.background")
|
||||
h3 {{$t('background')}}
|
||||
.col-4.item-wrapper
|
||||
.col-12.col-md-4.item-wrapper
|
||||
.box(:class='{white: costumeItems.shield && costumeItems.shield.indexOf("base_0") === -1}')
|
||||
div(:class="`shop_${costumeItems.shield}`")
|
||||
h3 {{$t('offHand')}}
|
||||
.row.pet-mount-row
|
||||
.col-6
|
||||
.col-12.col-md-6
|
||||
h2.text-center(v-once) {{ $t('pets') }}
|
||||
.well.pet-mount-well
|
||||
.row.col-12
|
||||
.col-4
|
||||
.col-12.col-md-4
|
||||
.box(:class='{white: user.items.currentPet}')
|
||||
.pet(:class="`Pet-${user.items.currentPet}`")
|
||||
.col-8
|
||||
.col-12.col-md-8
|
||||
div
|
||||
| {{ formatAnimal(user.items.currentPet, 'pet') }}
|
||||
div
|
||||
|
|
@ -212,14 +212,14 @@ div
|
|||
div
|
||||
strong {{ $t('beastMasterProgress') }}:
|
||||
| {{ beastMasterProgress(user.items.pets) }}
|
||||
.col-6
|
||||
.col-12.col-md-6
|
||||
h2.text-center(v-once) {{ $t('mounts') }}
|
||||
.well.pet-mount-well
|
||||
.row.col-12
|
||||
.col-4
|
||||
.col-12.col-md-4
|
||||
.box(:class='{white: user.items.currentMount}')
|
||||
.mount(:class="`Mount_Icon_${user.items.currentMount}`")
|
||||
.col-8
|
||||
.col-12.col-md-8
|
||||
div
|
||||
| {{ formatAnimal(user.items.currentMount, 'mount') }}
|
||||
div
|
||||
|
|
@ -231,14 +231,14 @@ div
|
|||
#attributes.row
|
||||
hr.col-12
|
||||
h2.col-12 {{$t('attributes')}}
|
||||
.col-6(v-for="(statInfo, stat) in stats")
|
||||
.col-12.col-md-6(v-for="(statInfo, stat) in stats")
|
||||
.row.col-12.stats-column
|
||||
.col-4.attribute-label
|
||||
.col-12.col-md-4.attribute-label
|
||||
span.hint(:popover-title='$t(statInfo.title)', popover-placement='right',
|
||||
:popover='$t(statInfo.popover)', popover-trigger='mouseenter')
|
||||
.stat-title(:class='stat') {{ $t(statInfo.title) }}
|
||||
strong.number {{ statsComputed[stat] }}
|
||||
.col-6
|
||||
.col-12.col-md-6
|
||||
ul.bonus-stats
|
||||
li
|
||||
strong {{$t('level')}}:
|
||||
|
|
@ -257,26 +257,26 @@ div
|
|||
| {{user.stats.buffs[stat]}}
|
||||
#allocation(v-if='user._id === userLoggedIn._id')
|
||||
.row.title-row
|
||||
.col-6
|
||||
.col-12.col-md-6
|
||||
h3(v-if='userLevel100Plus', v-once, v-html="$t('noMoreAllocate')")
|
||||
h3(v-if='user.stats.points || userLevel100Plus')
|
||||
| {{$t('pointsAvailable')}}
|
||||
.counter.badge(v-if='user.stats.points || userLevel100Plus')
|
||||
| {{user.stats.points}}
|
||||
.col-6
|
||||
.col-12.col-md-6
|
||||
.float-right
|
||||
toggle-switch(:label="$t('autoAllocation')",
|
||||
v-model='user.preferences.automaticAllocation',
|
||||
@change='userset({"preferences.automaticAllocation": Boolean(user.preferences.automaticAllocation), "preferences.allocationMode": "taskbased"})')
|
||||
|
||||
.row
|
||||
.col-3(v-for='(statInfo, stat) in allocateStatsList')
|
||||
.col-12.col-md-3(v-for='(statInfo, stat) in allocateStatsList')
|
||||
.box.white.row.col-12
|
||||
.col-12
|
||||
div(:class='stat') {{ $t(stats[stat].title) }}
|
||||
.number {{ user.stats[stat] }}
|
||||
.points {{$t('pts')}}
|
||||
.col-4
|
||||
.col-12.col-md-4
|
||||
.up(v-if='user.stats.points', @click='allocate(stat)')
|
||||
send-gems-modal(:userReceivingGems='userReceivingGems')
|
||||
</template>
|
||||
|
|
@ -293,11 +293,23 @@ div
|
|||
background: #f9f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
.message-icon svg {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.gift-icon svg {
|
||||
height: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profile-actions {
|
||||
float: right;
|
||||
margin-right: 1em;
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ export function togglePinnedItem (store, params) {
|
|||
|
||||
export function castSpell (store, params) {
|
||||
let spellUrl = `/api/v3/user/class/cast/${params.key}`;
|
||||
|
||||
if (params.targetId) spellUrl += `?targetId=${params.targetId}`;
|
||||
|
||||
return axios.post(spellUrl);
|
||||
|
|
|
|||
|
|
@ -91,10 +91,6 @@ export default function () {
|
|||
startingPage: '',
|
||||
subPage: '',
|
||||
},
|
||||
flagChatOptions: {
|
||||
message: {},
|
||||
groupId: '',
|
||||
},
|
||||
challengeOptions: {
|
||||
cloning: false,
|
||||
tasksToClone: {},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"tip5": "It’s best to use skills that cause buffs in the morning so they last longer.",
|
||||
"tip6": "Hover over a task and click the dots to access advanced task controls, such as the ability to push tasks to the top/bottom of your list.",
|
||||
"tip7": "Some backgrounds connect perfectly if Party members use the same background. Ex: Mountain Lake, Pagodas, and Rolling Hills.",
|
||||
"tip8": "Send a Message to someone by clicking their name in chat and the clicking the envelope icon at the top of their profile!",
|
||||
"tip8": "Send a Message to someone by clicking their name in chat and then clicking the envelope icon at the top of their profile!",
|
||||
"tip9": "Use the filters + search bar in the Inventories, Shops, Guilds, and Challenges to quickly find what you want.",
|
||||
"tip10": "You can win gems by competing in Challenges. New ones are added every day!",
|
||||
"tip11": "Having more than four Party members increases accountability!",
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 286 B |
|
After Width: | Height: | Size: 264 B |
|
After Width: | Height: | Size: 230 B |
|
After Width: | Height: | Size: 350 B |
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 267 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -540,7 +540,7 @@ api.updatePassword = {
|
|||
|
||||
/**
|
||||
* @api {post} /api/v3/user/reset-password Reset password
|
||||
* @apiDescription Reset the user password
|
||||
* @apiDescription Send the user an email to let them reset their password
|
||||
* @apiName ResetPassword
|
||||
* @apiGroup User
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1566,7 +1566,7 @@ api.userSell = {
|
|||
*
|
||||
* @apiErrorExample {json}
|
||||
* {"success":false,"error":"BadRequest","message":"Path string is required"}
|
||||
8 {"success":false,"error":"NotAuthorized","message":"Full set already unlocked."}
|
||||
* {"success":false,"error":"NotAuthorized","message":"Full set already unlocked."}
|
||||
*/
|
||||
api.userUnlock = {
|
||||
method: 'POST',
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ api.subscribeSuccess = {
|
|||
if (req.query.noRedirect) {
|
||||
res.respond(200);
|
||||
} else {
|
||||
res.redirect('/');
|
||||
res.redirect('/user/settings/subscription');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -147,7 +147,7 @@ api.subscribeCancel = {
|
|||
if (req.query.noRedirect) {
|
||||
res.respond(200);
|
||||
} else {
|
||||
res.redirect('/');
|
||||
res.redirect('/user/settings/subscription');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||