Merge branch 'develop' into release

This commit is contained in:
Sabe Jones
2018-08-16 14:41:09 -05:00
13 changed files with 5910 additions and 7105 deletions

12840
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,7 @@
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0",
"babel-runtime": "^6.11.6",
"bcrypt": "^2.0.0",
"bcrypt": "^3.0.0",
"body-parser": "^1.18.3",
"bootstrap": "^4.1.1",
"bootstrap-vue": "^2.0.0-rc.9",
@@ -35,7 +35,7 @@
"coupon-code": "^0.4.5",
"cross-env": "^5.1.5",
"css-loader": "^0.28.11",
"csv-stringify": "^2.1.0",
"csv-stringify": "^3.0.0",
"cwait": "^1.1.1",
"domain-middleware": "~0.1.0",
"express": "^4.16.3",
@@ -43,7 +43,7 @@
"express-validator": "^5.2.0",
"extract-text-webpack-plugin": "^3.0.2",
"glob": "^7.1.2",
"got": "^8.3.1",
"got": "^9.0.0",
"gulp": "^4.0.0",
"gulp-babel": "^7.0.1",
"gulp-imagemin": "^4.1.0",
@@ -95,7 +95,7 @@
"url-loader": "^1.0.0",
"useragent": "^2.1.9",
"uuid": "^3.0.1",
"validator": "^9.4.1",
"validator": "^10.5.0",
"vinyl-buffer": "^1.0.1",
"vue": "^2.5.16",
"vue-loader": "^14.2.2",
@@ -163,19 +163,19 @@
"expect.js": "^0.3.1",
"http-proxy-middleware": "^0.18.0",
"istanbul": "^1.1.0-alpha.1",
"karma": "^2.0.2",
"karma": "^3.0.0",
"karma-babel-preprocessor": "^7.0.0",
"karma-chai-plugins": "^0.9.0",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage": "^1.1.2",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.5",
"karma-sinon-chai": "^1.3.4",
"karma-sinon-chai": "^2.0.0",
"karma-sinon-stub-promise": "^1.0.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "^3.0.0",
"lcov-result-merger": "^2.0.0",
"lcov-result-merger": "^3.0.0",
"mocha": "^5.1.1",
"monk": "^6.0.6",
"nightwatch": "^0.9.21",

View File

@@ -1,3 +1,5 @@
import { IncomingWebhook } from '@slack/client';
import nconf from 'nconf';
import {
createAndPopulateGroup,
generateUser,
@@ -15,8 +17,6 @@ import { getMatchesByWordArray } from '../../../../../website/server/libs/string
import bannedWords from '../../../../../website/server/libs/bannedWords';
import guildsAllowingBannedWords from '../../../../../website/server/libs/guildsAllowingBannedWords';
import * as email from '../../../../../website/server/libs/email';
import { IncomingWebhook } from '@slack/client';
import nconf from 'nconf';
const BASE_URL = nconf.get('BASE_URL');
@@ -80,12 +80,14 @@ describe('POST /chat', () => {
});
});
it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => {
let userWithChatRevoked = await member.update({'flags.chatRevoked': true});
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('chatPrivilegesRevoked'),
describe('mute user', () => {
it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => {
const userWithChatRevoked = await member.update({'flags.chatRevoked': true});
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('chatPrivilegesRevoked'),
});
});
});
@@ -273,6 +275,7 @@ describe('POST /chat', () => {
message: t('chatPrivilegesRevoked'),
});
// @TODO: The next test should not depend on this. We should reset the user test in a beforeEach
// Restore chat privileges to continue testing
user.flags.chatRevoked = false;
await user.update({'flags.chatRevoked': false});

View File

@@ -58,7 +58,7 @@
.checkbox
label
input(type='checkbox', v-if='hero.flags', v-model='hero.flags.chatRevoked')
| Chat Privileges Revoked
strong Chat Privileges Revoked
.form-group
.checkbox
label
@@ -103,7 +103,6 @@
</style>
<script>
// import keys from 'lodash/keys';
import each from 'lodash/each';
import markdownDirective from 'client/directives/markdown';
@@ -174,7 +173,7 @@ export default {
async loadHero (uuid, heroIndex) {
this.currentHeroIndex = heroIndex;
let hero = await this.$store.dispatch('hall:getHero', { uuid });
this.hero = Object.assign({}, this.hero, hero);
this.hero = Object.assign({}, hero);
if (!this.hero.flags) {
this.hero.flags = {
chatRevoked: false,
@@ -204,9 +203,6 @@ export default {
startingPage: 'profile',
});
},
userLevelStyle () {
// @TODO: implement
},
},
};
</script>

View File

@@ -88,6 +88,7 @@ import axios from 'axios';
import moment from 'moment';
import throttle from 'lodash/throttle';
import { toNextLevel } from '../../common/script/statHelpers';
import { shouldDo } from '../../common/script/cron';
import { mapState } from 'client/libs/store';
import notifications from 'client/mixins/notifications';
@@ -222,7 +223,12 @@ export default {
userExp (after, before) {
if (after === before) return;
if (this.user.stats.lvl === 0) return;
this.exp(after - before);
let exp = after - before;
if (exp < -50) { // recalculate exp if user level up
exp = toNextLevel(this.user.stats.lvl - 1) - before + after;
}
this.exp(exp);
},
userGp (after, before) {
if (after === before) return;

View File

@@ -9,36 +9,50 @@ div(v-if='user.stats.lvl > 10')
.col-4.mana
.img(:class='`shop_${spell.key} shop-sprite item-img`')
drawer(
:title="$t('skillsTitle')",
v-if='user.stats.class && !user.preferences.disableClasses',
v-mousePosition="30",
@mouseMoved="mouseMoved($event)",
:openStatus='openStatus',
@toggled='drawerToggled'
)
div(slot="drawer-slider")
.container.spell-container
.row
.col-3(
@click='castStart(skill)',
v-for='(skill, key) in spells[user.stats.class]',
v-if='user.stats.lvl >= skill.lvl',
v-b-popover.hover.auto='skill.notes()')
.spell.col-12.row
.col-8.details
a(:class='{"disabled": spellDisabled(key)}')
div.img(:class='`shop_${skill.key} shop-sprite item-img`')
span.title {{skill.text()}}
.col-4.mana
.mana-text
.svg-icon(v-html="icons.mana")
div {{skill.mana}}
.drawer-wrapper.d-flex.justify-content-center
drawer(
:title="$t('skillsTitle')",
v-if='user.stats.class && !user.preferences.disableClasses',
v-mousePosition="30",
@mouseMoved="mouseMoved($event)",
:openStatus='openStatus',
@toggled='drawerToggled'
)
div(slot="drawer-slider")
.container.spell-container
.row
.col-12.col-md-3(
@click='castStart(skill)',
v-for='(skill, key) in spells[user.stats.class]',
v-if='user.stats.lvl >= skill.lvl',
v-b-popover.hover.auto='skill.notes()')
.spell.col-12.row
.col-8.details
a(:class='{"disabled": spellDisabled(key)}')
div.img(:class='`shop_${skill.key} shop-sprite item-img`')
span.title {{skill.text()}}
.col-4.mana
.mana-text
.svg-icon(v-html="icons.mana")
div {{skill.mana}}
</template>
<style lang="scss" scoped>
.drawer-wrapper {
width: 100vw;
position: fixed;
bottom: 0;
left: 0;
z-index: 19;
.drawer-container {
left: auto !important;
right: auto !important;
min-width: 60%;
}
}
.drawer-container {
left: calc((100% - 978px) / 2);
}
.drawer-slider {

View File

@@ -52,8 +52,14 @@
// @TODO: Implement new message header here when we fix the above
.new-message-row(v-if='selectedConversation.key && !user.flags.chatRevoked')
textarea(v-model='newMessage', @keyup.ctrl.enter='sendPrivateMessage()')
button.btn.btn-secondary(@click='sendPrivateMessage()') Send
textarea(
v-model='newMessage',
@keyup.ctrl.enter='sendPrivateMessage()',
maxlength='3000'
)
button.btn.btn-secondary(@click='sendPrivateMessage()') {{$t('send')}}
.row
span.ml-3 {{ currentLength }} / 3000
</template>
<style lang="scss" scoped>
@@ -322,6 +328,9 @@ export default {
return conversation.name.toLowerCase().indexOf(this.search.toLowerCase()) !== -1;
});
},
currentLength () {
return this.newMessage.length;
},
placeholderTexts () {
if (this.user.flags.chatRevoked) {
return {

View File

@@ -47,6 +47,6 @@ export function round (number, nDigits) {
}
export function getXPMessage (val) {
if (val < -50) return; // don't show when they level up (resetting their exp)
if (val < -50) return; // don't show when they multi-level up (resetting their exp)
return `${getSign(val)} ${round(val)}`;
}

View File

@@ -2,7 +2,7 @@
module.exports = {
invalidAttribute: '"<%= attr %>" is not a valid Stat.',
statsObjectRequired: '"stats" update is required',
statsObjectRequired: '"stats" object is required',
missingTypeParam: '"req.params.type" is required.',
missingKeyParam: '"req.params.key" is required.',

View File

@@ -159,6 +159,7 @@ api.postChat = {
}
if (!group) throw new NotFound(res.t('groupNotFound'));
if (group.privacy !== 'private' && user.flags.chatRevoked) {
throw new NotAuthorized(res.t('chatPrivilegesRevoked'));
}

View File

@@ -273,6 +273,7 @@ api.updateHero = {
if (updateData.auth && updateData.auth.blocked === false) {
hero.auth.blocked = false;
}
if (updateData.flags && _.isBoolean(updateData.flags.chatRevoked)) hero.flags.chatRevoked = updateData.flags.chatRevoked;
let savedHero = await hero.save();

View File

@@ -1445,8 +1445,8 @@ api.userSell = {
* @apiParam (Query) {String} path Full path to unlock. See "content" API call for list of items.
*
* @apiParamExample {curl}
* curl -x POST http://habitica.com/api/v3/user/unlock?path=background.midnight_clouds
* curl -x POST http://habitica.com/api/v3/user/unlock?path=hair.color.midnight
* curl -X POST http://habitica.com/api/v3/user/unlock?path=background.midnight_clouds
* curl -X POST http://habitica.com/api/v3/user/unlock?path=hair.color.midnight
*
* @apiSuccess {Object} data.purchased
* @apiSuccess {Object} data.items

View File

@@ -5,24 +5,24 @@ import { authWithHeaders } from '../../../middlewares/auth';
let api = {};
/**
* @api {post} /api/v3/user/allocate Allocate a single attribute point
* @api {post} /api/v3/user/allocate Allocate a single Stat Point (previously called Attribute Point)
* @apiName UserAllocate
* @apiGroup User
*
* @apiParam (Body) {String="str","con","int","per"} stat Query parameter - Default ='str'
* @apiParam (Query) {String="str","con","int","per"} stat The Stat to increase. Default is 'str'
*
* @apiParamExample {json} Example request
* {"stat":"int"}
* @apiParamExample {curl}
* curl -X POST -d "" https://habitica.com/api/v3/user/allocate?stat=int
*
* @apiSuccess {Object} data Returns stats from the user profile
* @apiSuccess {Object} data Returns stats and notifications from the user profile
*
* @apiError {NotAuthorized} NoPoints Not enough attribute points to increment a stat.
* @apiError {NotAuthorized} NoPoints You don't have enough Stat Points.
*
* @apiErrorExample {json}
* {
* "success": false,
* "error": "NotAuthorized",
* "message": "You don't have enough attribute points."
* "message": "You don't have enough Stat Points."
* }
*/
api.allocate = {
@@ -40,7 +40,7 @@ api.allocate = {
};
/**
* @api {post} /api/v3/user/allocate-bulk Allocate multiple attribute points
* @api {post} /api/v3/user/allocate-bulk Allocate multiple Stat Points
* @apiName UserAllocateBulk
* @apiGroup User
*
@@ -49,22 +49,22 @@ api.allocate = {
* @apiParamExample {json} Example request
* {
* stats: {
* 'int': int,
* 'str': int,
* 'con': int,
* 'per': int,
* },
* "int": int,
* "str": str,
* "con": con,
* "per": per
* }
* }
*
* @apiSuccess {Object} data Returns stats from the user profile
* @apiSuccess {Object} data Returns stats and notifications from the user profile
*
* @apiError {NotAuthorized} NoPoints Not enough attribute points to increment a stat.
* @apiError {NotAuthorized} NoPoints You don't have enough Stat Points.
*
* @apiErrorExample {json}
* {
* "success": false,
* "error": "NotAuthorized",
* "message": "You don't have enough attribute points."
* "message": "You don't have enough Stat Points."
* }
*/
api.allocateBulk = {
@@ -82,7 +82,7 @@ api.allocateBulk = {
};
/**
* @api {post} /api/v3/user/allocate-now Allocate all attribute points
* @api {post} /api/v3/user/allocate-now Allocate all Stat Points
* @apiDescription Uses the user's chosen automatic allocation method, or if none, assigns all to STR. Note: will return success, even if there are 0 points to allocate.
* @apiName UserAllocateNow
* @apiGroup User
@@ -119,7 +119,8 @@ api.allocateBulk = {
* "per": 0,
* "str": 0,
* "con": 0
* }
* },
* "notifications": [ .... ],
* }
* }
*