mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
Merge branch 'develop' into release
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import {
|
||||
validateItemPath,
|
||||
getDefaultOwnedGear,
|
||||
castItemVal,
|
||||
} from '../../../../../website/server/libs/items/utils';
|
||||
|
||||
describe('Items Utils', () => {
|
||||
@@ -64,4 +65,49 @@ describe('Items Utils', () => {
|
||||
expect(validateItemPath('items.quests.invalid')).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('castItemVal', () => {
|
||||
it('returns the item val untouched if not an item path', () => {
|
||||
expect(castItemVal('notitems.gear.owned.item', 'a string')).to.equal('a string');
|
||||
});
|
||||
|
||||
it('returns the item val untouched if an unsupported path', () => {
|
||||
expect(castItemVal('items.gear.equipped.weapon', 'a string')).to.equal('a string');
|
||||
expect(castItemVal('items.currentPet', 'a string')).to.equal('a string');
|
||||
expect(castItemVal('items.special.snowball', 'a string')).to.equal('a string');
|
||||
});
|
||||
|
||||
it('converts values for pets paths to numbers', () => {
|
||||
expect(castItemVal('items.pets.Wolf-CottonCandyPink', '5')).to.equal(5);
|
||||
expect(castItemVal('items.pets.Wolf-Invalid', '5')).to.equal(5);
|
||||
});
|
||||
|
||||
it('converts values for eggs paths to numbers', () => {
|
||||
expect(castItemVal('items.eggs.LionCub', '5')).to.equal(5);
|
||||
expect(castItemVal('items.eggs.Armadillo', '5')).to.equal(5);
|
||||
expect(castItemVal('items.eggs.NotAnArmadillo', '5')).to.equal(5);
|
||||
});
|
||||
|
||||
it('converts values for hatching potions paths to numbers', () => {
|
||||
expect(castItemVal('items.hatchingPotions.Base', '5')).to.equal(5);
|
||||
expect(castItemVal('items.hatchingPotions.StarryNight', '5')).to.equal(5);
|
||||
expect(castItemVal('items.hatchingPotions.Invalid', '5')).to.equal(5);
|
||||
});
|
||||
|
||||
it('converts values for food paths to numbers', () => {
|
||||
expect(castItemVal('items.food.Cake_Base', '5')).to.equal(5);
|
||||
expect(castItemVal('items.food.Cake_Invalid', '5')).to.equal(5);
|
||||
});
|
||||
|
||||
it('converts values for mounts paths to numbers', () => {
|
||||
expect(castItemVal('items.mounts.Cactus-Base', '5')).to.equal(5);
|
||||
expect(castItemVal('items.mounts.Aether-Invisible', '5')).to.equal(5);
|
||||
expect(castItemVal('items.mounts.Aether-Invalid', '5')).to.equal(5);
|
||||
});
|
||||
|
||||
it('converts values for quests paths to numbers', () => {
|
||||
expect(castItemVal('items.quests.atom3', '5')).to.equal(5);
|
||||
expect(castItemVal('items.quests.invalid', '5')).to.equal(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,4 +60,10 @@ describe('GET /inbox/messages', () => {
|
||||
|
||||
expect(messages.length).to.equal(4);
|
||||
});
|
||||
|
||||
it('returns only the messages of one conversation', async () => {
|
||||
const messages = await user.get(`/inbox/messages?conversation=${otherUser.id}`);
|
||||
|
||||
expect(messages.length).to.equal(3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,6 +63,38 @@ describe('Groups DELETE /tasks/:id', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('removes deleted taskʾs approval pending notifications from managers', async () => {
|
||||
await user.post(`/groups/${guild._id}/add-manager`, {
|
||||
managerId: member2._id,
|
||||
});
|
||||
await user.put(`/tasks/${task._id}/`, {
|
||||
requiresApproval: true,
|
||||
});
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
await member2.sync();
|
||||
expect(user.notifications.length).to.equal(2);
|
||||
expect(user.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
|
||||
expect(member2.notifications.length).to.equal(2);
|
||||
expect(member2.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
|
||||
|
||||
await member2.del(`/tasks/${task._id}`);
|
||||
|
||||
await user.sync();
|
||||
await member2.sync();
|
||||
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(member2.notifications.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('unlinks assigned user', async () => {
|
||||
await user.del(`/tasks/${task._id}`);
|
||||
|
||||
|
||||
@@ -53,18 +53,29 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
|
||||
it('approves an assigned user', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
|
||||
await member.sync();
|
||||
|
||||
expect(member.notifications.length).to.equal(2);
|
||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED');
|
||||
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
expect(member.notifications[1].type).to.equal('SCORED_TASK');
|
||||
expect(member.notifications.length).to.equal(3);
|
||||
expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED');
|
||||
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
expect(member.notifications[2].type).to.equal('SCORED_TASK');
|
||||
expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
|
||||
memberTasks = await member.get('/tasks/user');
|
||||
syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
expect(syncedTask.group.approval.approved).to.be.true;
|
||||
expect(syncedTask.group.approval.approvingUser).to.equal(user._id);
|
||||
@@ -77,18 +88,28 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
await member.sync();
|
||||
|
||||
expect(member.notifications.length).to.equal(2);
|
||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED');
|
||||
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
expect(member.notifications[1].type).to.equal('SCORED_TASK');
|
||||
expect(member.notifications.length).to.equal(3);
|
||||
expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED');
|
||||
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
expect(member.notifications[2].type).to.equal('SCORED_TASK');
|
||||
expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||
|
||||
memberTasks = await member.get('/tasks/user');
|
||||
syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
expect(syncedTask.group.approval.approved).to.be.true;
|
||||
expect(syncedTask.group.approval.approvingUser).to.equal(member2._id);
|
||||
@@ -132,6 +153,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
await expect(user.post(`/tasks/${task._id}/approve/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
@@ -141,6 +172,17 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('prevents approving a task if it is not waiting for approval', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
await expect(user.post(`/tasks/${task._id}/approve/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalWasNotRequested'),
|
||||
});
|
||||
});
|
||||
|
||||
it('completes master task when single-completion task is approved', async () => {
|
||||
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'shared completion todo',
|
||||
@@ -151,6 +193,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||
|
||||
let groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`);
|
||||
@@ -172,6 +224,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||
|
||||
let member2Tasks = await member2.get('/tasks/user');
|
||||
@@ -193,6 +255,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||
|
||||
let groupTasks = await user.get(`/tasks/group/${guild._id}`);
|
||||
@@ -214,6 +286,25 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
let member2Tasks = await member2.get('/tasks/user');
|
||||
let member2SyncedTask = find(member2Tasks, findAssignedTask);
|
||||
await expect(member2.post(`/tasks/${member2SyncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member2._id}`);
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('marks as task as needing more work', async () => {
|
||||
it('marks a task as needing more work', async () => {
|
||||
const initialNotifications = member.notifications.length;
|
||||
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
@@ -77,7 +77,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
|
||||
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
||||
|
||||
// Check that the notification is correct
|
||||
expect(member.notifications.length).to.equal(initialNotifications + 1);
|
||||
expect(member.notifications.length).to.equal(initialNotifications + 2);
|
||||
const notification = member.notifications[member.notifications.length - 1];
|
||||
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
||||
|
||||
@@ -131,7 +131,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
|
||||
expect(syncedTask.group.approval.requested).to.equal(false);
|
||||
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
||||
|
||||
expect(member.notifications.length).to.equal(initialNotifications + 1);
|
||||
expect(member.notifications.length).to.equal(initialNotifications + 2);
|
||||
const notification = member.notifications[member.notifications.length - 1];
|
||||
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
||||
|
||||
@@ -167,6 +167,17 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
|
||||
@@ -129,6 +129,13 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskApprovalHasBeenRequested'),
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||
|
||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
|
||||
@@ -113,6 +113,17 @@ describe('POST /tasks/:taskId/assign/:memberId', () => {
|
||||
expect(syncedTask).to.exist;
|
||||
});
|
||||
|
||||
it('sends a notification to assigned user', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
await member.sync();
|
||||
|
||||
let groupTask = await user.get(`/tasks/group/${guild._id}`);
|
||||
|
||||
expect(member.notifications.length).to.equal(1);
|
||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_ASSIGNED');
|
||||
expect(member.notifications[0].taskId).to.equal(groupTask._id);
|
||||
});
|
||||
|
||||
it('assigns a task to multiple users', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${task._id}/assign/${member2._id}`);
|
||||
|
||||
@@ -86,6 +86,13 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => {
|
||||
expect(syncedTask).to.not.exist;
|
||||
});
|
||||
|
||||
it('removes task assignment notification from unassigned user', async () => {
|
||||
await user.post(`/tasks/${task._id}/unassign/${member._id}`);
|
||||
|
||||
await member.sync();
|
||||
expect(member.notifications.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('unassigns a user and only that user from a task', async () => {
|
||||
await user.post(`/tasks/${task._id}/assign/${member2._id}`);
|
||||
|
||||
|
||||
44
test/api/v4/inbox/GET-inbox-conversations.test.js
Normal file
44
test/api/v4/inbox/GET-inbox-conversations.test.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('GET /inbox/conversations', () => {
|
||||
let user;
|
||||
let otherUser;
|
||||
let thirdUser;
|
||||
|
||||
before(async () => {
|
||||
[user, otherUser, thirdUser] = await Promise.all([generateUser(), generateUser(), generateUser()]);
|
||||
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'first',
|
||||
});
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: otherUser.id,
|
||||
message: 'second',
|
||||
});
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: thirdUser.id,
|
||||
message: 'third',
|
||||
});
|
||||
await otherUser.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'fourth',
|
||||
});
|
||||
|
||||
// message to yourself
|
||||
await user.post('/members/send-private-message', {
|
||||
toUserId: user.id,
|
||||
message: 'fifth',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the conversations', async () => {
|
||||
const result = await user.get('/inbox/conversations');
|
||||
|
||||
expect(result.length).to.be.equal(3);
|
||||
expect(result[0].user).to.be.equal(user.profile.name);
|
||||
expect(result[0].username).to.be.equal(user.auth.local.username);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
<template lang="pug">
|
||||
b-modal#rebirth(:title="$t('modalAchievement')", size='md', :hide-footer="true", @hidden="reloadPage()")
|
||||
b-modal#rebirth(:title="$t('modalAchievement')", size='md', :hide-footer="true")
|
||||
.modal-body
|
||||
.col-12
|
||||
// @TODO: +achievementAvatar('sun',0)
|
||||
@@ -41,9 +41,6 @@
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'rebirth');
|
||||
},
|
||||
reloadPage () {
|
||||
window.location.reload(true);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -376,6 +376,8 @@ export default {
|
||||
openMemberProgressModal (member) {
|
||||
this.$root.$emit('habitica:challenge:member-progress', {
|
||||
progressMemberId: member._id,
|
||||
isLeader: this.isLeader,
|
||||
isAdmin: this.isAdmin,
|
||||
});
|
||||
},
|
||||
async exportChallengeCsv () {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="pug">
|
||||
b-modal#challenge-member-modal(title="User Progress", size='lg')
|
||||
.row.award-row
|
||||
.row.award-row(v-if='isLeader || isAdmin')
|
||||
.col-12.text-center
|
||||
button.btn.btn-primary(v-once, @click='closeChallenge()') {{ $t('awardWinners') }}
|
||||
.row
|
||||
@@ -37,12 +37,16 @@ export default {
|
||||
reward: [],
|
||||
},
|
||||
memberId: '',
|
||||
isLeader: false,
|
||||
isAdmin: false,
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica:challenge:member-progress', (data) => {
|
||||
if (!data.progressMemberId) return;
|
||||
this.memberId = data.progressMemberId;
|
||||
this.isLeader = data.isLeader;
|
||||
this.isAdmin = data.isAdmin;
|
||||
this.$root.$emit('bv::show::modal', 'challenge-member-modal');
|
||||
});
|
||||
},
|
||||
|
||||
@@ -16,54 +16,7 @@
|
||||
h1 {{ $t('groupTasksTitle') }}
|
||||
// @TODO: Abstract to component!
|
||||
.col-12.col-md-4
|
||||
.input-group
|
||||
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
|
||||
.filter-panel(v-if="isFilterPanelOpen")
|
||||
.tags-category(v-for="tagsType in tagsByType", v-if="tagsType.tags.length > 0", :key="tagsType.key")
|
||||
.tags-header.col-12
|
||||
strong(v-once) {{ $t(tagsType.key) }}
|
||||
a.d-block(v-if="tagsType.key === 'tags' && !editingTags", @click="editTags()") {{ $t('editTags2') }}
|
||||
.tags-list.container.col-12
|
||||
.row(:class="{'no-gutters': !editingTags}")
|
||||
template(v-if="editingTags && tagsType.key === 'tags'")
|
||||
.col-12.col-md-6(v-for="(tag, tagIndex) in tagsSnap")
|
||||
.inline-edit-input-group.tag-edit-item.input-group
|
||||
input.tag-edit-input.inline-edit-input.form-control(type="text", :value="tag.name")
|
||||
.input-group-append(@click="removeTag(tagIndex)")
|
||||
.svg-icon.destroy-icon(v-html="icons.destroy")
|
||||
.col-12.col-md-6
|
||||
input.new-tag-item.edit-tag-item.inline-edit-input.form-control(type="text", :placeholder="$t('newTag')", @keydown.enter="addTag($event)", v-model="newTag")
|
||||
template(v-else)
|
||||
.col-12.col-md-6(v-for="(tag, tagIndex) in tagsType.tags")
|
||||
.custom-control.custom-checkbox
|
||||
input.custom-control-input(
|
||||
type="checkbox",
|
||||
:checked="isTagSelected(tag)",
|
||||
@change="toggleTag(tag)",
|
||||
:id="`tag-${tagsType.key}-${tagIndex}`",
|
||||
)
|
||||
label.custom-control-label(:for="`tag-${tagsType.key}-${tagIndex}`") {{ tag.name }}
|
||||
|
||||
.filter-panel-footer.clearfix
|
||||
template(v-if="editingTags === true")
|
||||
.text-center
|
||||
a.mr-3.btn-filters-primary(@click="saveTags()", v-once) {{ $t('saveEdits') }}
|
||||
a.btn-filters-secondary(@click="cancelTagsEditing()", v-once) {{ $t('cancel') }}
|
||||
template(v-else)
|
||||
.float-left
|
||||
a.btn-filters-danger(@click="resetFilters()", v-once) {{ $t('resetFilters') }}
|
||||
.float-right
|
||||
a.mr-3.btn-filters-primary(@click="applyFilters()", v-once) {{ $t('applyFilters') }}
|
||||
a.btn-filters-secondary(@click="closeFilterPanel()", v-once) {{ $t('cancel') }}
|
||||
span.input-group-append
|
||||
button.btn.btn-secondary.filter-button(
|
||||
type="button",
|
||||
@click="toggleFilterPanel()",
|
||||
:class="{'filter-button-open': selectedTags.length > 0}",
|
||||
)
|
||||
.d-flex.align-items-center
|
||||
span(v-once) {{ $t('filter') }}
|
||||
.svg-icon.filter-icon(v-html="icons.filter")
|
||||
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
|
||||
.create-task-area.d-flex(v-if='canCreateTasks')
|
||||
transition(name="slide-tasks-btns")
|
||||
.d-flex(v-if="openCreateBtn")
|
||||
@@ -99,10 +52,6 @@
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
@import '~client/assets/scss/create-task.scss';
|
||||
|
||||
.user-tasks-page {
|
||||
padding-top: 31px;
|
||||
}
|
||||
|
||||
.tasks-navigation {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
@@ -114,133 +63,6 @@
|
||||
margin-right: 8px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.dropdown-icon-item .svg-icon {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
button.btn.btn-secondary.filter-button {
|
||||
box-shadow: none;
|
||||
border-radius: 2px;
|
||||
border: 1px solid $gray-400 !important;
|
||||
|
||||
&:hover, &:active, &:focus, &.open {
|
||||
box-shadow: none;
|
||||
border-color: $purple-500 !important;
|
||||
color: $gray-50 !important;
|
||||
}
|
||||
|
||||
&.filter-button-open {
|
||||
color: $purple-200 !important;
|
||||
|
||||
.filter-icon {
|
||||
color: $purple-200 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-icon {
|
||||
height: 10px;
|
||||
width: 12px;
|
||||
color: $gray-50;
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-panel {
|
||||
position: absolute;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
max-width: 40vw;
|
||||
width: 100%;
|
||||
z-index: 9999;
|
||||
background: $white;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||
top: 44px;
|
||||
left: 20vw;
|
||||
font-size: 14px;
|
||||
line-height: 1.43;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.tags-category {
|
||||
border-bottom: 1px solid $gray-600;
|
||||
padding-bottom: 24px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.tags-header {
|
||||
flex-basis: 96px;
|
||||
flex-shrink: 0;
|
||||
|
||||
a {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
color: $blue-10;
|
||||
margin-top: 4px;
|
||||
|
||||
&:focus, &:hover, &:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tag-edit-input {
|
||||
border-bottom: 1px solid $gray-500 !important;
|
||||
|
||||
&:focus, &:focus ~ .input-group-append {
|
||||
border-color: $purple-500 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.new-tag-item {
|
||||
width: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center left 10px;
|
||||
border-bottom: 1px solid $gray-500 !important;
|
||||
background-size: 10px 10px;
|
||||
padding-left: 40px;
|
||||
background-image: url(~client/assets/svg/for-css/positive.svg);
|
||||
}
|
||||
|
||||
.tag-edit-item .input-group-append {
|
||||
border-bottom: 1px solid $gray-500 !important;
|
||||
|
||||
&:focus {
|
||||
border-color: $purple-500;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-control-label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.filter-panel-footer {
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
|
||||
a {
|
||||
&:focus, &:hover, &:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-filters-danger {
|
||||
color: $red-50;
|
||||
}
|
||||
|
||||
.btn-filters-primary {
|
||||
color: $blue-10;
|
||||
}
|
||||
|
||||
.btn-filters-secondary {
|
||||
color: $gray-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-label {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<template lang="pug">
|
||||
base-notification(
|
||||
:can-remove="canRemove",
|
||||
:has-icon="false",
|
||||
:notification="notification",
|
||||
:read-after-click="true",
|
||||
@click="action",
|
||||
)
|
||||
div(slot="content", v-html="notification.data.message")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseNotification from './base';
|
||||
|
||||
export default {
|
||||
props: ['notification', 'canRemove'],
|
||||
components: {
|
||||
BaseNotification,
|
||||
},
|
||||
methods: {
|
||||
action () {
|
||||
this.$router.push({ name: 'tasks'});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -87,6 +87,7 @@ import CHALLENGE_INVITATION from './notifications/challengeInvitation';
|
||||
import QUEST_INVITATION from './notifications/questInvitation';
|
||||
import GROUP_TASK_APPROVAL from './notifications/groupTaskApproval';
|
||||
import GROUP_TASK_APPROVED from './notifications/groupTaskApproved';
|
||||
import GROUP_TASK_ASSIGNED from './notifications/groupTaskAssigned';
|
||||
import UNALLOCATED_STATS_POINTS from './notifications/unallocatedStatsPoints';
|
||||
import NEW_MYSTERY_ITEMS from './notifications/newMysteryItems';
|
||||
import CARD_RECEIVED from './notifications/cardReceived';
|
||||
@@ -102,7 +103,7 @@ export default {
|
||||
// One component for each type
|
||||
NEW_STUFF, GROUP_TASK_NEEDS_WORK,
|
||||
GUILD_INVITATION, PARTY_INVITATION, CHALLENGE_INVITATION,
|
||||
QUEST_INVITATION, GROUP_TASK_APPROVAL, GROUP_TASK_APPROVED,
|
||||
QUEST_INVITATION, GROUP_TASK_APPROVAL, GROUP_TASK_APPROVED, GROUP_TASK_ASSIGNED,
|
||||
UNALLOCATED_STATS_POINTS, NEW_MYSTERY_ITEMS, CARD_RECEIVED,
|
||||
NEW_INBOX_MESSAGE, NEW_CHAT_MESSAGE,
|
||||
WorldBoss: WORLD_BOSS,
|
||||
@@ -118,7 +119,7 @@ export default {
|
||||
openStatus: undefined,
|
||||
actionableNotifications: [
|
||||
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
|
||||
'QUEST_INVITATION', 'GROUP_TASK_NEEDS_WORK',
|
||||
'QUEST_INVITATION', 'GROUP_TASK_NEEDS_WORK', 'GROUP_TASK_APPROVAL',
|
||||
],
|
||||
// A list of notifications handled by this component,
|
||||
// listed in the order they should appear in the notifications panel.
|
||||
@@ -126,7 +127,7 @@ export default {
|
||||
handledNotifications: [
|
||||
'NEW_STUFF', 'GROUP_TASK_NEEDS_WORK',
|
||||
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
|
||||
'QUEST_INVITATION', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED',
|
||||
'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED',
|
||||
'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
|
||||
'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS',
|
||||
'VERIFY_USERNAME',
|
||||
|
||||
@@ -132,11 +132,6 @@ const NOTIFICATIONS = {
|
||||
label: ($t) => `${$t('achievement')}: ${$t('gearAchievementNotification')}`,
|
||||
modalId: 'ultimate-gear',
|
||||
},
|
||||
REBIRTH_ACHIEVEMENT: {
|
||||
label: ($t) => `${$t('achievement')}: ${$t('rebirthBegan')}`,
|
||||
achievement: true,
|
||||
modalId: 'rebirth',
|
||||
},
|
||||
GUILD_JOINED_ACHIEVEMENT: {
|
||||
label: ($t) => `${$t('achievement')}: ${$t('joinedGuild')}`,
|
||||
achievement: true,
|
||||
@@ -360,18 +355,7 @@ export default {
|
||||
this.playSound(config.sound);
|
||||
}
|
||||
|
||||
if (type === 'REBIRTH_ACHIEVEMENT') {
|
||||
// reload if the user hasn't clicked on the notification
|
||||
const timeOut = setTimeout(() => {
|
||||
window.location.reload(true);
|
||||
}, 60000);
|
||||
|
||||
this.text(config.label(this.$t), () => {
|
||||
// prevent the current reload timeout
|
||||
clearTimeout(timeOut);
|
||||
this.$root.$emit('bv::show::modal', config.modalId);
|
||||
}, false);
|
||||
} else if (forceToModal) {
|
||||
if (forceToModal) {
|
||||
this.$root.$emit('bv::show::modal', config.modalId);
|
||||
} else {
|
||||
this.text(config.label(this.$t), () => {
|
||||
@@ -573,8 +557,11 @@ export default {
|
||||
}, this.user.preferences.suppressModals.streak);
|
||||
this.playSound('Achievement_Unlocked');
|
||||
break;
|
||||
case 'ULTIMATE_GEAR_ACHIEVEMENT':
|
||||
case 'REBIRTH_ACHIEVEMENT':
|
||||
this.playSound('Achievement_Unlocked');
|
||||
this.$root.$emit('bv::show::modal', 'rebirth');
|
||||
break;
|
||||
case 'ULTIMATE_GEAR_ACHIEVEMENT':
|
||||
case 'GUILD_JOINED_ACHIEVEMENT':
|
||||
case 'CHALLENGE_JOINED_ACHIEVEMENT':
|
||||
case 'INVITED_FRIEND_ACHIEVEMENT':
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
|
||||
) {{ $t('buyNow') }}
|
||||
|
||||
div.limitedTime(v-if="item.event")
|
||||
div.limitedTime(v-if="item.event && item.owned == null")
|
||||
span.svg-icon.inline.icon-16(v-html="icons.clock")
|
||||
span.limitedString {{ limitedString }}
|
||||
|
||||
@@ -397,6 +397,10 @@
|
||||
|
||||
this.$emit('buyPressed', this.item);
|
||||
this.hideDialog();
|
||||
|
||||
if (this.item.key === 'rebirth_orb') {
|
||||
window.location.reload(true);
|
||||
}
|
||||
},
|
||||
purchaseGems () {
|
||||
if (this.item.key === 'rebirth_orb') {
|
||||
|
||||
@@ -5,7 +5,7 @@ div
|
||||
slot(name="itemBadge", :item="item", :emptyItem="emptyItem")
|
||||
|
||||
span.badge.badge-pill.badge-item.badge-clock(
|
||||
v-if="item.event && showEventBadge",
|
||||
v-if="item.event && item.owned == null && showEventBadge",
|
||||
)
|
||||
span.svg-icon.inline.clock(v-html="icons.clock")
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<template lang="pug">
|
||||
.tags-popup
|
||||
.tags-category.d-flex
|
||||
.tags-category.d-flex(
|
||||
v-for="tagsType in tagsByType",
|
||||
v-if="tagsType.tags.length > 0 || tagsType.key === 'tags'",
|
||||
:key="tagsType.key"
|
||||
)
|
||||
.tags-header
|
||||
strong(v-once) {{ $t('tags') }}
|
||||
strong(v-once) {{ $t(tagsType.key) }}
|
||||
.tags-list.container
|
||||
.row
|
||||
.col-4(v-for="tag in tags")
|
||||
.col-4(v-for="(tag, tagIndex) in tagsType.tags")
|
||||
.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", :value="tag.id", v-model="selectedTags", :id="`tag-${tag.id}`")
|
||||
label.custom-control-label(:title="tag.name", :for="`tag-${tag.id}`", v-markdown="tag.name")
|
||||
@@ -103,6 +107,34 @@ export default {
|
||||
selectedTags: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tagsByType () {
|
||||
const tagsByType = {
|
||||
challenges: {
|
||||
key: 'challenges',
|
||||
tags: [],
|
||||
},
|
||||
groups: {
|
||||
key: 'groups',
|
||||
tags: [],
|
||||
},
|
||||
user: {
|
||||
key: 'tags',
|
||||
tags: [],
|
||||
},
|
||||
};
|
||||
this.$props.tags.forEach(t => {
|
||||
if (t.group) {
|
||||
tagsByType.groups.tags.push(t);
|
||||
} else if (t.challenge) {
|
||||
tagsByType.challenges.tags.push(t);
|
||||
} else {
|
||||
tagsByType.user.tags.push(t);
|
||||
}
|
||||
});
|
||||
return tagsByType;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedTags () {
|
||||
this.$emit('input', this.selectedTags);
|
||||
|
||||
@@ -159,6 +159,15 @@
|
||||
| {{ $t(frequency) }}
|
||||
|
||||
.option.group-options(v-if='groupId')
|
||||
.form-group(v-if="task.type === 'todo'")
|
||||
label(v-once) {{ $t('sharedCompletion') }}
|
||||
b-dropdown.inline-dropdown(:text="$t(sharedCompletion)")
|
||||
b-dropdown-item(
|
||||
v-for="completionOption in ['recurringCompletion', 'singleCompletion', 'allAssignedCompletion']",
|
||||
:key="completionOption",
|
||||
@click="sharedCompletion = completionOption",
|
||||
:class="{active: sharedCompletion === completionOption}"
|
||||
) {{ $t(completionOption) }}
|
||||
.form-group.row
|
||||
label.col-12(v-once) {{ $t('assignedTo') }}
|
||||
.col-12.mt-2
|
||||
@@ -179,23 +188,12 @@
|
||||
|
||||
.row
|
||||
button.btn.btn-primary(@click.stop.prevent="showAssignedSelect = !showAssignedSelect") {{$t('close')}}
|
||||
|
||||
.option.group-options(v-if='groupId')
|
||||
.form-group
|
||||
label(v-once) {{ $t('approvalRequired') }}
|
||||
toggle-switch.d-inline-block(
|
||||
:checked="requiresApproval",
|
||||
@change="updateRequiresApproval"
|
||||
)
|
||||
.form-group(v-if="task.type === 'todo'")
|
||||
label(v-once) {{ $t('sharedCompletion') }}
|
||||
b-dropdown.inline-dropdown(:text="$t(sharedCompletion)")
|
||||
b-dropdown-item(
|
||||
v-for="completionOption in ['recurringCompletion', 'singleCompletion', 'allAssignedCompletion']",
|
||||
:key="completionOption",
|
||||
@click="sharedCompletion = completionOption",
|
||||
:class="{active: sharedCompletion === completionOption}"
|
||||
) {{ $t(completionOption) }}
|
||||
|
||||
.advanced-settings(v-if="task.type !== 'reward'")
|
||||
.advanced-settings-toggle.d-flex.justify-content-between.align-items-center(@click = "showAdvancedOptions = !showAdvancedOptions")
|
||||
@@ -360,8 +358,8 @@
|
||||
margin-top: 12px;
|
||||
position: relative;
|
||||
|
||||
label {
|
||||
max-height: 30px;
|
||||
.custom-control-label p {
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,7 +709,7 @@ export default {
|
||||
calendar: calendarIcon,
|
||||
}),
|
||||
requiresApproval: false, // We can't set task.group fields so we use this field to toggle
|
||||
sharedCompletion: 'recurringCompletion',
|
||||
sharedCompletion: 'singleCompletion',
|
||||
members: [],
|
||||
memberNamesById: {},
|
||||
assignedMembers: [],
|
||||
@@ -842,7 +840,7 @@ export default {
|
||||
});
|
||||
this.assignedMembers = [];
|
||||
if (this.task.group && this.task.group.assignedUsers) this.assignedMembers = this.task.group.assignedUsers;
|
||||
if (this.task.group) this.sharedCompletion = this.task.group.sharedCompletion || 'recurringCompletion';
|
||||
if (this.task.group) this.sharedCompletion = this.task.group.sharedCompletion || 'singleCompletion';
|
||||
}
|
||||
|
||||
// @TODO: This whole component is mutating a prop and that causes issues. We need to not copy the prop similar to group modals
|
||||
@@ -926,7 +924,6 @@ export default {
|
||||
|
||||
// TODO Fix up permissions on task.group so we don't have to keep doing these hacks
|
||||
if (this.groupId) {
|
||||
this.task.group.assignedUsers = this.assignedMembers;
|
||||
this.task.requiresApproval = this.requiresApproval;
|
||||
this.task.group.approval.required = this.requiresApproval;
|
||||
this.task.sharedCompletion = this.sharedCompletion;
|
||||
@@ -954,6 +951,7 @@ export default {
|
||||
});
|
||||
});
|
||||
Promise.all(promises);
|
||||
this.task.group.assignedUsers = this.assignedMembers;
|
||||
this.$emit('taskCreated', this.task);
|
||||
} else {
|
||||
this.createTask(this.task);
|
||||
|
||||
@@ -201,6 +201,7 @@
|
||||
|
||||
.custom-control-label {
|
||||
margin-left: 10px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.filter-panel-footer {
|
||||
|
||||
@@ -51,10 +51,8 @@ export async function set (store, changes) {
|
||||
}
|
||||
}
|
||||
|
||||
axios.put('/api/v4/user', changes);
|
||||
// TODO
|
||||
// .then((res) => console.log('set', res))
|
||||
// .catch((err) => console.error('set', err));
|
||||
let response = await axios.put('/api/v4/user', changes);
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
export async function sleep (store) {
|
||||
|
||||
@@ -285,7 +285,8 @@
|
||||
"claim": "Claim",
|
||||
"removeClaim": "Remove Claim",
|
||||
"onlyGroupLeaderCanManageSubscription": "Only the group leader can manage the group's subscription",
|
||||
"yourTaskHasBeenApproved": "Your task <span class=\"notification-green\"><%= taskText %></span> has been approved.",
|
||||
"youHaveBeenAssignedTask": "<%= managerName %> has assigned you the task <span class=\"notification-bold\"><%= taskText %></span>.",
|
||||
"yourTaskHasBeenApproved": "Your task <span class=\"notification-green notification-bold\"><%= taskText %></span> has been approved.",
|
||||
"taskNeedsWork": "<span class=\"notification-bold\"><%= managerName %></span> marked <span class=\"notification-bold\"><%= taskText %></span> as needing additional work.",
|
||||
"userHasRequestedTaskApproval": "<span class=\"notification-bold\"><%= user %></span> requests approval for <span class=\"notification-bold\"><%= taskName %></span>",
|
||||
"approve": "Approve",
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
"habitCounterDown": "Negative Counter (Resets <%= frequency %>)",
|
||||
"taskRequiresApproval": "This task must be approved before you can complete it. Approval has already been requested",
|
||||
"taskApprovalHasBeenRequested": "Approval has been requested",
|
||||
"taskApprovalWasNotRequested": "Only a task waiting for approval can be marked as needing more work",
|
||||
"taskApprovalWasNotRequested": "Approval has not been requested for this task.",
|
||||
"approvals": "Approvals",
|
||||
"approvalRequired": "Needs Approval",
|
||||
"repeatZero": "Daily is never due",
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
import _ from 'lodash';
|
||||
import apiError from '../../libs/apiError';
|
||||
import validator from 'validator';
|
||||
import { validateItemPath } from '../../libs/items/utils';
|
||||
import {
|
||||
validateItemPath,
|
||||
castItemVal,
|
||||
} from '../../libs/items/utils';
|
||||
|
||||
|
||||
let api = {};
|
||||
@@ -271,7 +274,7 @@ api.updateHero = {
|
||||
hero.markModified('items.pets');
|
||||
}
|
||||
if (updateData.itemPath && updateData.itemVal && validateItemPath(updateData.itemPath)) {
|
||||
_.set(hero, updateData.itemPath, updateData.itemVal); // Sanitization at 5c30944 (deemed unnecessary)
|
||||
_.set(hero, updateData.itemPath, castItemVal(updateData.itemPath, updateData.itemVal)); // Sanitization at 5c30944 (deemed unnecessary)
|
||||
}
|
||||
|
||||
if (updateData.auth && updateData.auth.blocked === true) {
|
||||
|
||||
@@ -12,6 +12,7 @@ let api = {};
|
||||
* @apiDescription Get inbox messages for a user
|
||||
*
|
||||
* @apiParam (Query) {Number} page Load the messages of the selected Page - 10 Messages per Page
|
||||
* @apiParam (Query) {GUID} conversation Loads only the messages of a conversation
|
||||
*
|
||||
* @apiSuccess {Array} data An array of inbox messages
|
||||
*/
|
||||
@@ -22,9 +23,10 @@ api.getInboxMessages = {
|
||||
async handler (req, res) {
|
||||
const user = res.locals.user;
|
||||
const page = req.query.page;
|
||||
const conversation = req.query.conversation;
|
||||
|
||||
const userInbox = await inboxLib.getUserInbox(user, {
|
||||
page,
|
||||
page, conversation,
|
||||
});
|
||||
|
||||
res.respond(200, userInbox);
|
||||
|
||||
@@ -206,6 +206,14 @@ api.assignTask = {
|
||||
let message = res.t('userIsClamingTask', {username: user.profile.name, task: task.text});
|
||||
const newMessage = group.sendChat(message);
|
||||
promises.push(newMessage.save());
|
||||
} else {
|
||||
const taskText = task.text;
|
||||
const managerName = user.profile.name;
|
||||
|
||||
assignedUser.addNotification('GROUP_TASK_ASSIGNED', {
|
||||
message: res.t('youHaveBeenAssignedTask', {managerName, taskText}),
|
||||
taskId: task._id,
|
||||
});
|
||||
}
|
||||
|
||||
promises.push(group.syncTask(task, assignedUser));
|
||||
@@ -261,6 +269,15 @@ api.unassignTask = {
|
||||
|
||||
await group.unlinkTask(task, assignedUser);
|
||||
|
||||
let notificationIndex = assignedUser.notifications.findIndex(function findNotification (notification) {
|
||||
return notification && notification.data && notification.type === 'GROUP_TASK_ASSIGNED' && notification.data.taskId === task._id;
|
||||
});
|
||||
|
||||
if (notificationIndex !== -1) {
|
||||
assignedUser.notifications.splice(notificationIndex, 1);
|
||||
await assignedUser.save();
|
||||
}
|
||||
|
||||
res.respond(200, task);
|
||||
},
|
||||
};
|
||||
@@ -308,6 +325,9 @@ api.approveTask = {
|
||||
|
||||
if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
if (task.group.approval.approved === true) throw new NotAuthorized(res.t('canOnlyApproveTaskOnce'));
|
||||
if (!task.group.approval.requested) {
|
||||
throw new NotAuthorized(res.t('taskApprovalWasNotRequested'));
|
||||
}
|
||||
|
||||
task.group.approval.dateApproved = new Date();
|
||||
task.group.approval.approvingUser = user._id;
|
||||
|
||||
@@ -72,4 +72,25 @@ api.clearMessages = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {get} /inbox/conversations Get the conversations for a user
|
||||
* @apiName conversations
|
||||
* @apiGroup Inbox
|
||||
* @apiDescription Get the conversations for a user
|
||||
*
|
||||
* @apiSuccess {Array} data An array of inbox conversations
|
||||
*/
|
||||
api.conversations = {
|
||||
method: 'GET',
|
||||
middlewares: [authWithHeaders()],
|
||||
url: '/inbox/conversations',
|
||||
async handler (req, res) {
|
||||
const user = res.locals.user;
|
||||
|
||||
const result = await inboxLib.listConversations(user);
|
||||
|
||||
res.respond(200, result);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = api;
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import { inboxModel as Inbox } from '../../models/message';
|
||||
import {inboxModel as Inbox} from '../../models/message';
|
||||
import {
|
||||
model as User,
|
||||
} from '../../models/user';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import keyBy from 'lodash/keyBy';
|
||||
|
||||
const PM_PER_PAGE = 10;
|
||||
|
||||
export async function getUserInbox (user, options = {asArray: true, page: 0}) {
|
||||
export async function getUserInbox (user, options = {asArray: true, page: 0, conversation: null}) {
|
||||
if (typeof options.asArray === 'undefined') {
|
||||
options.asArray = true;
|
||||
}
|
||||
|
||||
const findObj = {ownerId: user._id};
|
||||
|
||||
if (options.conversation) {
|
||||
findObj.uuid = options.conversation;
|
||||
}
|
||||
|
||||
let query = Inbox
|
||||
.find({ownerId: user._id})
|
||||
.find(findObj)
|
||||
.sort({timestamp: -1});
|
||||
|
||||
if (typeof options.page !== 'undefined') {
|
||||
@@ -29,12 +40,45 @@ export async function getUserInbox (user, options = {asArray: true, page: 0}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function listConversations (user) {
|
||||
let query = Inbox
|
||||
.aggregate([
|
||||
{
|
||||
$match: {
|
||||
ownerId: user._id,
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: '$uuid',
|
||||
timestamp: {$max: '$timestamp'}, // sort before group doesn't work - use the max value to sort it again after
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const conversationsList = orderBy(await query.exec(), ['timestamp'], ['desc']).map(c => c._id);
|
||||
|
||||
const users = await User.find({_id: {$in: conversationsList}})
|
||||
.select('_id profile.name auth.local.username')
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
const usersMap = keyBy(users, '_id');
|
||||
const conversations = conversationsList.map(userId => ({
|
||||
uuid: usersMap[userId]._id,
|
||||
user: usersMap[userId].profile.name,
|
||||
username: usersMap[userId].auth.local.username,
|
||||
}));
|
||||
|
||||
return conversations;
|
||||
}
|
||||
|
||||
export async function getUserInboxMessage (user, messageId) {
|
||||
return Inbox.findOne({ownerId: user._id, _id: messageId}).exec();
|
||||
}
|
||||
|
||||
export async function deleteMessage (user, messageId) {
|
||||
const message = await Inbox.findOne({_id: messageId, ownerId: user._id }).exec();
|
||||
const message = await Inbox.findOne({_id: messageId, ownerId: user._id}).exec();
|
||||
if (!message) return false;
|
||||
await Inbox.remove({_id: message._id, ownerId: user._id}).exec();
|
||||
|
||||
|
||||
@@ -55,3 +55,23 @@ export function validateItemPath (itemPath) {
|
||||
return Boolean(shared.content.quests[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// When passed a value of an item in the user object it'll convert the
|
||||
// value to the correct format.
|
||||
// Example a numeric string like "5" applied to a food item (expecting an interger)
|
||||
// will be converted to the number 5
|
||||
// TODO cast the correct value for `items.gear.owned`
|
||||
export function castItemVal (itemPath, itemVal) {
|
||||
if (
|
||||
itemPath.indexOf('items.pets') === 0 ||
|
||||
itemPath.indexOf('items.eggs') === 0 ||
|
||||
itemPath.indexOf('items.hatchingPotions') === 0 ||
|
||||
itemPath.indexOf('items.food') === 0 ||
|
||||
itemPath.indexOf('items.mounts') === 0 ||
|
||||
itemPath.indexOf('items.quests') === 0
|
||||
) {
|
||||
return Number(itemVal);
|
||||
}
|
||||
|
||||
return itemVal;
|
||||
}
|
||||
@@ -1341,7 +1341,7 @@ schema.methods.syncTask = async function groupSyncTask (taskToSync, user) {
|
||||
matchingTask.group.id = taskToSync.group.id;
|
||||
matchingTask.userId = user._id;
|
||||
matchingTask.group.taskId = taskToSync._id;
|
||||
user.tasksOrder[`${taskToSync.type}s`].push(matchingTask._id);
|
||||
user.tasksOrder[`${taskToSync.type}s`].unshift(matchingTask._id);
|
||||
} else {
|
||||
_.merge(matchingTask, syncableAttrs(taskToSync));
|
||||
// Make sure the task is in user.tasksOrder
|
||||
@@ -1419,9 +1419,29 @@ schema.methods.removeTask = async function groupRemoveTask (task) {
|
||||
$set: {'group.broken': 'TASK_DELETED'},
|
||||
}, {multi: true}).exec();
|
||||
|
||||
// Get Managers
|
||||
const managerIds = Object.keys(group.managers);
|
||||
managerIds.push(group.leader);
|
||||
const managers = await User.find({_id: managerIds}, 'notifications').exec(); // Use this method so we can get access to notifications
|
||||
|
||||
// Remove old notifications
|
||||
let removalPromises = [];
|
||||
managers.forEach((manager) => {
|
||||
let notificationIndex = manager.notifications.findIndex(function findNotification (notification) {
|
||||
return notification && notification.data && notification.data.groupTaskId === task._id && notification.type === 'GROUP_TASK_APPROVAL';
|
||||
});
|
||||
|
||||
if (notificationIndex !== -1) {
|
||||
manager.notifications.splice(notificationIndex, 1);
|
||||
removalPromises.push(manager.save());
|
||||
}
|
||||
});
|
||||
|
||||
removeFromArray(group.tasksOrder[`${task.type}s`], task._id);
|
||||
group.markModified('tasksOrder');
|
||||
return await group.save();
|
||||
removalPromises.push(group.save());
|
||||
|
||||
return await Promise.all(removalPromises);
|
||||
};
|
||||
|
||||
// Returns true if the user has reached the spam message limit
|
||||
|
||||
@@ -113,7 +113,7 @@ export let TaskSchema = new Schema({
|
||||
requested: {$type: Boolean, default: false},
|
||||
requestedDate: {$type: Date},
|
||||
},
|
||||
sharedCompletion: {$type: String, enum: _.values(SHARED_COMPLETION), default: SHARED_COMPLETION.default},
|
||||
sharedCompletion: {$type: String, enum: _.values(SHARED_COMPLETION), default: SHARED_COMPLETION.single},
|
||||
},
|
||||
|
||||
reminders: [{
|
||||
|
||||
@@ -15,6 +15,7 @@ const NOTIFICATION_TYPES = [
|
||||
'CRON',
|
||||
'GROUP_TASK_APPROVAL',
|
||||
'GROUP_TASK_APPROVED',
|
||||
'GROUP_TASK_ASSIGNED',
|
||||
'GROUP_TASK_NEEDS_WORK',
|
||||
'LOGIN_INCENTIVE',
|
||||
'GROUP_INVITE_ACCEPTED',
|
||||
|
||||
Reference in New Issue
Block a user