mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-15 13:47:33 +01:00
Merge branch 'develop' into release
This commit is contained in:
12840
package-lock.json
generated
12840
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -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",
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)}`;
|
||||
}
|
||||
@@ -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.',
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": [ .... ],
|
||||
* }
|
||||
* }
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user