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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -47,6 +47,6 @@ export function round (number, nDigits) {
} }
export function getXPMessage (val) { 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)}`; return `${getSign(val)} ${round(val)}`;
} }

View File

@@ -2,7 +2,7 @@
module.exports = { module.exports = {
invalidAttribute: '"<%= attr %>" is not a valid Stat.', invalidAttribute: '"<%= attr %>" is not a valid Stat.',
statsObjectRequired: '"stats" update is required', statsObjectRequired: '"stats" object is required',
missingTypeParam: '"req.params.type" is required.', missingTypeParam: '"req.params.type" is required.',
missingKeyParam: '"req.params.key" 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) throw new NotFound(res.t('groupNotFound'));
if (group.privacy !== 'private' && user.flags.chatRevoked) { if (group.privacy !== 'private' && user.flags.chatRevoked) {
throw new NotAuthorized(res.t('chatPrivilegesRevoked')); throw new NotAuthorized(res.t('chatPrivilegesRevoked'));
} }

View File

@@ -273,6 +273,7 @@ api.updateHero = {
if (updateData.auth && updateData.auth.blocked === false) { if (updateData.auth && updateData.auth.blocked === false) {
hero.auth.blocked = false; hero.auth.blocked = false;
} }
if (updateData.flags && _.isBoolean(updateData.flags.chatRevoked)) hero.flags.chatRevoked = updateData.flags.chatRevoked; if (updateData.flags && _.isBoolean(updateData.flags.chatRevoked)) hero.flags.chatRevoked = updateData.flags.chatRevoked;
let savedHero = await hero.save(); 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. * @apiParam (Query) {String} path Full path to unlock. See "content" API call for list of items.
* *
* @apiParamExample {curl} * @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=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=hair.color.midnight
* *
* @apiSuccess {Object} data.purchased * @apiSuccess {Object} data.purchased
* @apiSuccess {Object} data.items * @apiSuccess {Object} data.items

View File

@@ -5,24 +5,24 @@ import { authWithHeaders } from '../../../middlewares/auth';
let api = {}; 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 * @apiName UserAllocate
* @apiGroup User * @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 * @apiParamExample {curl}
* {"stat":"int"} * 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} * @apiErrorExample {json}
* { * {
* "success": false, * "success": false,
* "error": "NotAuthorized", * "error": "NotAuthorized",
* "message": "You don't have enough attribute points." * "message": "You don't have enough Stat Points."
* } * }
*/ */
api.allocate = { 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 * @apiName UserAllocateBulk
* @apiGroup User * @apiGroup User
* *
@@ -49,22 +49,22 @@ api.allocate = {
* @apiParamExample {json} Example request * @apiParamExample {json} Example request
* { * {
* stats: { * stats: {
* 'int': int, * "int": int,
* 'str': int, * "str": str,
* 'con': int, * "con": con,
* 'per': int, * "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} * @apiErrorExample {json}
* { * {
* "success": false, * "success": false,
* "error": "NotAuthorized", * "error": "NotAuthorized",
* "message": "You don't have enough attribute points." * "message": "You don't have enough Stat Points."
* } * }
*/ */
api.allocateBulk = { 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. * @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 * @apiName UserAllocateNow
* @apiGroup User * @apiGroup User
@@ -119,7 +119,8 @@ api.allocateBulk = {
* "per": 0, * "per": 0,
* "str": 0, * "str": 0,
* "con": 0 * "con": 0
* } * },
* "notifications": [ .... ],
* } * }
* } * }
* *