mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
Group tasks ui picked (#7996)
* Added initial group tasks ui * Changed group compnent directory * Added group task checklist support * Added checklist support to ui * Fixed delete tags route * Added checklist routes to support new group tasks * Added assign user tag input * Added new group members autocomplete directive * Linked assign ui to api * Added styles * Limited tag use * Fixed line endings * Updated to new file structure * Fixed failing task tests * Updatd with new checklist logic and fixed columns * Added purchased info to group and prevented non purchased group from seeing new group tasks * Updated add task function * Added userid check back to tag routes * Marked tag tests as pending * Added comments to pending tests * Added back routes accidently deleted * Added locale strings * Other clarity fixes * Moved common task function to task service * Removed files from manifest * Fixed naming collision and remove logic * Removed group get tasks until live * Fixed test to check update task. Removed extra removeTask call. Synced updated checklists. Added purchased to noset * Fixed delete group task
This commit is contained in:
committed by
Matteo Pagliazzi
parent
6a82206f81
commit
285041cdee
@@ -43,7 +43,8 @@
|
||||
"smart-app-banner": "78ef9c0679723b25be1a0ae04f7b4aef7cbced4f",
|
||||
"habitica-markdown": "1.2.2",
|
||||
"pusher-js-auth": "^2.0.0",
|
||||
"pusher-websocket-iso": "pusher#^3.2.0"
|
||||
"pusher-websocket-iso": "pusher#^3.2.0",
|
||||
"taggle": "^1.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "1.3.9"
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('DELETE group /tasks/:taskId/checklist/:itemId', () => {
|
||||
let user, guild, task;
|
||||
|
||||
before(async () => {
|
||||
let {group, groupLeader} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
},
|
||||
members: 2,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
});
|
||||
|
||||
it('deletes a checklist item', async () => {
|
||||
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'daily',
|
||||
text: 'Daily with checklist',
|
||||
});
|
||||
|
||||
let savedTask = await user.post(`/tasks/${task._id}/checklist`, {text: 'Checklist Item 1', completed: false});
|
||||
|
||||
await user.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`);
|
||||
savedTask = await user.get(`/tasks/group/${guild._id}`);
|
||||
|
||||
expect(savedTask[0].checklist.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('does not work with habits', async () => {
|
||||
let habit = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'habit',
|
||||
text: 'habit with checklist',
|
||||
});
|
||||
|
||||
await expect(user.del(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('checklistOnlyDailyTodo'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not work with rewards', async () => {
|
||||
let reward = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'reward',
|
||||
text: 'reward with checklist',
|
||||
});
|
||||
|
||||
await expect(user.del(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('checklistOnlyDailyTodo'),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on task not found', async () => {
|
||||
await expect(user.del(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on checklist item not found', async () => {
|
||||
let createdTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'daily',
|
||||
text: 'daily with checklist',
|
||||
});
|
||||
|
||||
await expect(user.del(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('checklistItemNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST group /tasks/:taskId/checklist/', () => {
|
||||
let user, guild, task;
|
||||
|
||||
before(async () => {
|
||||
let {group, groupLeader} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
},
|
||||
members: 2,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
});
|
||||
|
||||
it('adds a checklist item to a task', async () => {
|
||||
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'daily',
|
||||
text: 'Daily with checklist',
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${task._id}/checklist`, {
|
||||
text: 'Checklist Item 1',
|
||||
ignored: false,
|
||||
_id: 123,
|
||||
});
|
||||
|
||||
let updatedTasks = await user.get(`/tasks/group/${guild._id}`);
|
||||
let updatedTask = updatedTasks[0];
|
||||
|
||||
expect(updatedTask.checklist.length).to.equal(1);
|
||||
expect(updatedTask.checklist[0].text).to.equal('Checklist Item 1');
|
||||
expect(updatedTask.checklist[0].completed).to.equal(false);
|
||||
expect(updatedTask.checklist[0].id).to.be.a('string');
|
||||
expect(updatedTask.checklist[0].id).to.not.equal('123');
|
||||
expect(updatedTask.checklist[0].ignored).to.be.an('undefined');
|
||||
});
|
||||
|
||||
it('does not add a checklist to habits', async () => {
|
||||
let habit = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'habit',
|
||||
text: 'habit with checklist',
|
||||
});
|
||||
|
||||
await expect(user.post(`/tasks/${habit._id}/checklist`, {
|
||||
text: 'Checklist Item 1',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('checklistOnlyDailyTodo'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not add a checklist to rewards', async () => {
|
||||
let reward = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'reward',
|
||||
text: 'reward with checklist',
|
||||
});
|
||||
|
||||
await expect(user.post(`/tasks/${reward._id}/checklist`, {
|
||||
text: 'Checklist Item 1',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('checklistOnlyDailyTodo'),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on task not found', async () => {
|
||||
await expect(user.post(`/tasks/${generateUUID()}/checklist`, {
|
||||
text: 'Checklist Item 1',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('PUT group /tasks/:taskId/checklist/:itemId', () => {
|
||||
let user, guild, task;
|
||||
|
||||
before(async () => {
|
||||
let {group, groupLeader} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
},
|
||||
members: 2,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
});
|
||||
|
||||
it('updates a checklist item', async () => {
|
||||
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'daily',
|
||||
text: 'Daily with checklist',
|
||||
});
|
||||
|
||||
let savedTask = await user.post(`/tasks/${task._id}/checklist`, {
|
||||
text: 'Checklist Item 1',
|
||||
completed: false,
|
||||
});
|
||||
|
||||
savedTask = await user.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, {
|
||||
text: 'updated',
|
||||
completed: true,
|
||||
_id: 123, // ignored
|
||||
});
|
||||
|
||||
expect(savedTask.checklist.length).to.equal(1);
|
||||
expect(savedTask.checklist[0].text).to.equal('updated');
|
||||
expect(savedTask.checklist[0].completed).to.equal(true);
|
||||
expect(savedTask.checklist[0].id).to.not.equal('123');
|
||||
});
|
||||
|
||||
it('fails on habits', async () => {
|
||||
let habit = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'habit',
|
||||
text: 'habit with checklist',
|
||||
});
|
||||
|
||||
await expect(user.put(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('checklistOnlyDailyTodo'),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on rewards', async () => {
|
||||
let reward = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'reward',
|
||||
text: 'reward with checklist',
|
||||
});
|
||||
|
||||
await expect(user.put(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('checklistOnlyDailyTodo'),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on task not found', async () => {
|
||||
await expect(user.put(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('taskNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on checklist item not found', async () => {
|
||||
let createdTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'daily',
|
||||
text: 'daily with checklist',
|
||||
});
|
||||
|
||||
await expect(user.put(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('checklistItemNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
// Currently we do not support adding tags to group original tasks, but if we do in the future, these tests will check
|
||||
xdescribe('DELETE group /tasks/:taskId/tags/:tagId', () => {
|
||||
let user, guild, task;
|
||||
|
||||
before(async () => {
|
||||
let {group, groupLeader} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
},
|
||||
members: 2,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
});
|
||||
|
||||
it('removes a tag from a task', async () => {
|
||||
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'habit',
|
||||
text: 'Task with tag',
|
||||
});
|
||||
|
||||
let tag = await user.post('/tags', {name: 'Tag 1'});
|
||||
|
||||
await user.post(`/tasks/${task._id}/tags/${tag.id}`);
|
||||
await user.del(`/tasks/${task._id}/tags/${tag.id}`);
|
||||
|
||||
let updatedTask = await user.get(`/tasks/group/${guild._id}`);
|
||||
|
||||
expect(updatedTask[0].tags.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('only deletes existing tags', async () => {
|
||||
let createdTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'habit',
|
||||
text: 'Task with tag',
|
||||
});
|
||||
|
||||
await expect(user.del(`/tasks/${createdTask._id}/tags/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('tagNotFound'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../../../helpers/api-integration/v3';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
// Currently we do not support adding tags to group original tasks, but if we do in the future, these tests will check
|
||||
xdescribe('POST group /tasks/:taskId/tags/:tagId', () => {
|
||||
let user, guild, task;
|
||||
|
||||
before(async () => {
|
||||
let {group, groupLeader} = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Guild',
|
||||
type: 'guild',
|
||||
},
|
||||
members: 2,
|
||||
});
|
||||
|
||||
guild = group;
|
||||
user = groupLeader;
|
||||
});
|
||||
|
||||
it('adds a tag to a task', async () => {
|
||||
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'habit',
|
||||
text: 'Task with tag',
|
||||
});
|
||||
|
||||
let tag = await user.post('/tags', {name: 'Tag 1'});
|
||||
let savedTask = await user.post(`/tasks/${task._id}/tags/${tag.id}`);
|
||||
|
||||
expect(savedTask.tags[0]).to.equal(tag.id);
|
||||
});
|
||||
|
||||
it('does not add a tag to a task twice', async () => {
|
||||
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'habit',
|
||||
text: 'Task with tag',
|
||||
});
|
||||
|
||||
let tag = await user.post('/tags', {name: 'Tag 1'});
|
||||
|
||||
await user.post(`/tasks/${task._id}/tags/${tag.id}`);
|
||||
|
||||
await expect(user.post(`/tasks/${task._id}/tags/${tag.id}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('alreadyTagged'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not add a non existing tag to a task', async () => {
|
||||
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||
type: 'habit',
|
||||
text: 'Task with tag',
|
||||
});
|
||||
|
||||
await expect(user.post(`/tasks/${task._id}/tags/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('invalidReqParams'),
|
||||
});
|
||||
});
|
||||
});
|
||||
217
website/client-old/css/groups.styl
Normal file
217
website/client-old/css/groups.styl
Normal file
@@ -0,0 +1,217 @@
|
||||
group-members-autocomplete
|
||||
.clearfix:before, .clearfix:after
|
||||
display block
|
||||
content ""
|
||||
line-height 0
|
||||
clear both
|
||||
|
||||
.taggle_list
|
||||
float left
|
||||
width 100%
|
||||
li
|
||||
float left
|
||||
display inline-block
|
||||
white-space nowrap
|
||||
font-weight 500
|
||||
margin-bottom 5px
|
||||
.taggle
|
||||
margin-right 8px
|
||||
background #E2E1DF
|
||||
padding 5px 10px
|
||||
border-radius 3px
|
||||
position relative
|
||||
cursor pointer
|
||||
transition all .3s
|
||||
-webkit-animation-duration 1s
|
||||
animation-duration 1s
|
||||
-webkit-animation-fill-mode both
|
||||
animation-fill-mode both
|
||||
.close
|
||||
font-size 1.1rem
|
||||
position absolute
|
||||
top 5px
|
||||
right 3px
|
||||
text-decoration none
|
||||
padding-left 2px
|
||||
padding-top 3px
|
||||
line-height 0.5
|
||||
color #ccc
|
||||
color rgba(0, 0, 0, 0.2)
|
||||
padding-bottom 4px
|
||||
display none
|
||||
border 0
|
||||
background none
|
||||
cursor pointer
|
||||
&:hover
|
||||
color $color-purple
|
||||
&:hover
|
||||
padding 5px
|
||||
padding-right 15px
|
||||
background #ccc
|
||||
transition all .3s
|
||||
& > .close
|
||||
display block
|
||||
.taggle_hot
|
||||
background #cac8c4
|
||||
|
||||
.taggle_input
|
||||
border none
|
||||
outline none
|
||||
font-size 16px
|
||||
font-weight 300
|
||||
padding 8px
|
||||
padding-left 0
|
||||
float left
|
||||
margin-top -5px
|
||||
background none
|
||||
width 100%
|
||||
max-width 100%
|
||||
|
||||
.taggle_placeholder
|
||||
position absolute
|
||||
color #CCC
|
||||
top 12px
|
||||
left 8px
|
||||
transition opacity, .25s
|
||||
-webkit-user-select none
|
||||
-moz-user-select none
|
||||
-ms-user-select none
|
||||
user-select none
|
||||
|
||||
.taggle_sizer
|
||||
padding 0
|
||||
margin 0
|
||||
position absolute
|
||||
top -500px
|
||||
z-index -1
|
||||
visibility hidden
|
||||
|
||||
textarea.input,
|
||||
.textarea.input
|
||||
border 0
|
||||
background #FDFDFD
|
||||
box-shadow inset 0 1px 2px rgba(0, 0, 0, 0.2), 0 1px 1px rgba(255, 255, 255, 0.7)
|
||||
min-height 60px
|
||||
padding 8px
|
||||
border-radius 3px
|
||||
color #555
|
||||
transition all .25s
|
||||
cursor text
|
||||
margin-bottom 10px
|
||||
position relative
|
||||
|
||||
.textarea.input:focus,
|
||||
.textarea.input.active,
|
||||
textarea.input:focus,
|
||||
textarea.input.active
|
||||
background #fff
|
||||
transition all .25s
|
||||
|
||||
.textarea.input,
|
||||
textarea.input
|
||||
height auto
|
||||
|
||||
.textarea
|
||||
&.tags
|
||||
position relative
|
||||
*
|
||||
box-sizing content-box
|
||||
|
||||
.placeholder_input
|
||||
position relative
|
||||
span
|
||||
position absolute
|
||||
color #AAA
|
||||
top 50%
|
||||
margin-top -11px
|
||||
left 10px
|
||||
input
|
||||
width 120px
|
||||
|
||||
.ui-autocomplete
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
max-height 200px
|
||||
width 99% !important
|
||||
|
||||
.ui-menu
|
||||
list-style none
|
||||
padding 2px
|
||||
margin 0
|
||||
display block
|
||||
outline none
|
||||
.ui-menu-item
|
||||
margin 0
|
||||
padding 0
|
||||
width 100%
|
||||
a
|
||||
text-decoration none
|
||||
display block
|
||||
padding 2px .4em
|
||||
line-height 1.5
|
||||
min-height 0
|
||||
font-weight normal
|
||||
color #8a8a8a
|
||||
cursor pointer
|
||||
&:hover
|
||||
color #fff
|
||||
background $color-purple
|
||||
|
||||
.ui-widget-content
|
||||
background #fff
|
||||
color $color-purple
|
||||
|
||||
.ui-state-hover,
|
||||
.ui-widget-content .ui-state-hover,
|
||||
.ui-widget-header .ui-state-hover,
|
||||
.ui-state-focus,
|
||||
.ui-widget-content .ui-state-focus,
|
||||
.ui-widget-header .ui-state-focus
|
||||
background $color-purple
|
||||
color #fff !important
|
||||
|
||||
.ui-state-hover a,
|
||||
.ui-state-hover a:hover,
|
||||
.ui-state-hover a:link,
|
||||
.ui-state-hover a:visited
|
||||
color #fff
|
||||
|
||||
.ui-state-active,
|
||||
.ui-widget-content .ui-state-active,
|
||||
.ui-widget-header .ui-state-active
|
||||
border 1px solid #aaaaaa
|
||||
background #ffffff
|
||||
font-weight normal
|
||||
color #212121
|
||||
|
||||
.ui-helper-hidden
|
||||
display none
|
||||
|
||||
.ui-helper-hidden-accessible
|
||||
border 0
|
||||
clip rect(0 0 0 0)
|
||||
height 1px
|
||||
margin -1px
|
||||
overflow hidden
|
||||
padding 0
|
||||
position absolute
|
||||
width 1px
|
||||
|
||||
.autocomplete
|
||||
max-height 200px
|
||||
position absolute
|
||||
top 66px
|
||||
background white
|
||||
width 99.5%
|
||||
left 0.25%
|
||||
z-index 2
|
||||
ul
|
||||
li
|
||||
display block
|
||||
padding 6px 8px
|
||||
|
||||
.autocomplete ul li.selected, .autocomplete ul li:hover
|
||||
background #ff6633
|
||||
color #fff
|
||||
cursor pointer
|
||||
@@ -21,6 +21,7 @@
|
||||
@import "./filters.styl"
|
||||
@import "./scrollbars.styl"
|
||||
@import "./game-pane.styl"
|
||||
@import "./groups.styl"
|
||||
@import "./npcs.styl"
|
||||
@import "./challenges.styl"
|
||||
@import "./classes.styl"
|
||||
|
||||
@@ -159,11 +159,11 @@ window.habitrpg = angular.module('habitrpg',
|
||||
url: '/:gid',
|
||||
templateUrl: 'partials/options.social.guilds.detail.html',
|
||||
title: env.t('titleGuilds'),
|
||||
controller: ['$scope', 'Groups', 'Chat', '$stateParams', 'Members', 'Challenges',
|
||||
function($scope, Groups, Chat, $stateParams, Members, Challenges){
|
||||
controller: ['$scope', 'Groups', 'Chat', '$stateParams', 'Members', 'Challenges', 'Tasks',
|
||||
function($scope, Groups, Chat, $stateParams, Members, Challenges, Tasks) {
|
||||
Groups.Group.get($stateParams.gid)
|
||||
.then(function (response) {
|
||||
$scope.group = response.data.data;
|
||||
$scope.obj = $scope.group = response.data.data;
|
||||
Chat.markChatSeen($scope.group._id);
|
||||
Members.getGroupMembers($scope.group._id)
|
||||
.then(function (response) {
|
||||
@@ -177,7 +177,16 @@ window.habitrpg = angular.module('habitrpg',
|
||||
.then(function (response) {
|
||||
$scope.group.challenges = response.data.data;
|
||||
});
|
||||
});
|
||||
//@TODO: Add this back when group tasks go live
|
||||
// return Tasks.getGroupTasks($scope.group._id);
|
||||
})
|
||||
// .then(function (response) {
|
||||
// var tasks = response.data.data;
|
||||
// tasks.forEach(function (element, index, array) {
|
||||
// if (!$scope.group[element.type + 's']) $scope.group[element.type + 's'] = [];
|
||||
// $scope.group[element.type + 's'].push(element);
|
||||
// })
|
||||
// });
|
||||
}]
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('groupMembersAutocomplete', groupMembersAutocomplete);
|
||||
|
||||
groupMembersAutocomplete.$inject = [
|
||||
'$parse',
|
||||
'$rootScope',
|
||||
];
|
||||
|
||||
function groupMembersAutocomplete($parse, $rootScope) {
|
||||
|
||||
return {
|
||||
templateUrl: 'partials/groups.members.autocomplete.html',
|
||||
compile: function (element, attrs) {
|
||||
var modelAccessor = $parse(attrs.ngModel);
|
||||
|
||||
return function (scope, element, attrs, controller) {
|
||||
var availableTags = _.pluck(scope.group.members, 'profile.name');
|
||||
var memberProfileNameToIdMap = _.object(_.map(scope.group.members, function(item) {
|
||||
return [item.profile.name, item.id]
|
||||
}));
|
||||
var memberIdToProfileNameMap = _.object(_.map(scope.group.members, function(item) {
|
||||
return [item.id, item.profile.name]
|
||||
}));
|
||||
|
||||
var currentTags = [];
|
||||
_.each(scope.task.group.assignedUsers, function(userId) { currentTags.push(memberIdToProfileNameMap[userId]) })
|
||||
|
||||
var taggle = new Taggle('taggle', {
|
||||
tags: currentTags,
|
||||
allowedTags: currentTags,
|
||||
allowDuplicates: false,
|
||||
onBeforeTagAdd: function(event, tag) {
|
||||
return confirm(window.env.t('confirmAddTag', {tag: tag}));
|
||||
},
|
||||
onTagAdd: function(event, tag) {
|
||||
$rootScope.$broadcast('addedGroupMember', memberProfileNameToIdMap[tag]);
|
||||
},
|
||||
onBeforeTagRemove: function(event, tag) {
|
||||
return confirm(window.env.t('confirmRemoveTag', {tag: tag}))
|
||||
},
|
||||
onTagRemove: function(event, tag) {
|
||||
$rootScope.$broadcast('removedGroupMember', memberProfileNameToIdMap[tag]);
|
||||
}
|
||||
});
|
||||
var container = taggle.getContainer();
|
||||
var input = taggle.getInput();
|
||||
|
||||
$(input).autocomplete({
|
||||
source: availableTags, // See jQuery UI documentaton for options
|
||||
appendTo: container,
|
||||
position: { at: "left bottom", of: container },
|
||||
select: function(event, data) {
|
||||
event.preventDefault();
|
||||
//Add the tag if user clicks
|
||||
if (event.which === 1) {
|
||||
taggle.add(data.item.value);
|
||||
var taggleTags = taggle.getTags();
|
||||
scope.$apply(function (scope) {
|
||||
// Change bound variable
|
||||
modelAccessor.assign(scope, taggleTags.values);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,13 @@
|
||||
habitrpg.controller('GroupTaskActionsCtrl', ['$scope', 'Shared', 'Tasks', 'User',
|
||||
function ($scope, Shared, Tasks, User) {
|
||||
$scope.assignedMembers = [];
|
||||
$scope.user = User.user;
|
||||
|
||||
$scope.$on('addedGroupMember', function(evt, userId) {
|
||||
Tasks.assignTask($scope.task.id, userId);
|
||||
});
|
||||
|
||||
$scope.$on('removedGroupMember', function(evt, userId) {
|
||||
Tasks.unAssignTask($scope.task.id, userId);
|
||||
});
|
||||
}]);
|
||||
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('groupTasksActions', hrpgSortTags);
|
||||
|
||||
hrpgSortTags.$inject = [
|
||||
];
|
||||
|
||||
function hrpgSortTags() {
|
||||
|
||||
return {
|
||||
scope: {
|
||||
task: '=',
|
||||
group: '=',
|
||||
},
|
||||
templateUrl: 'partials/groups.tasks.actions.html',
|
||||
controller: 'GroupTaskActionsCtrl',
|
||||
};
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,79 @@
|
||||
habitrpg.controller('GroupTasksCtrl', ['$scope', 'Shared', 'Tasks', 'User', function ($scope, Shared, Tasks, User) {
|
||||
$scope.editTask = Tasks.editTask;
|
||||
$scope.toggleBulk = Tasks.toggleBulk;
|
||||
$scope.cancelTaskEdit = Tasks.cancelTaskEdit;
|
||||
|
||||
function addTask (listDef, task) {
|
||||
var task = Shared.taskDefaults({text: task, type: listDef.type});
|
||||
//If the group has not been created, we bulk add tasks on save
|
||||
var group = $scope.obj;
|
||||
if (group._id) Tasks.createGroupTasks(group._id, task);
|
||||
if (!group[task.type + 's']) group[task.type + 's'] = [];
|
||||
group[task.type + 's'].unshift(task);
|
||||
delete listDef.newTask;
|
||||
};
|
||||
|
||||
$scope.addTask = function(listDef) {
|
||||
Tasks.addTasks(listDef, addTask);
|
||||
};
|
||||
|
||||
$scope.removeTask = function(task, group) {
|
||||
if (!Tasks.removeTask(task)) return;
|
||||
//We only pass to the api if the group exists, otherwise, the tasks only exist on the client
|
||||
if (group._id) Tasks.deleteTask(task._id);
|
||||
var index = group[task.type + 's'].indexOf(task);
|
||||
group[task.type + 's'].splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.saveTask = function(task, stayOpen, isSaveAndClose) {
|
||||
Tasks.saveTask (task, stayOpen, isSaveAndClose);
|
||||
Tasks.updateTask(task._id, task);
|
||||
};
|
||||
|
||||
$scope.shouldShow = function(task, list, prefs){
|
||||
return true;
|
||||
};
|
||||
|
||||
$scope.canEdit = function(task) {
|
||||
return true;
|
||||
};
|
||||
|
||||
/*
|
||||
------------------------
|
||||
Tags
|
||||
------------------------
|
||||
*/
|
||||
$scope.updateTaskTags = function (tagId, task) {
|
||||
var tagIndex = task.tags.indexOf(tagId);
|
||||
if (tagIndex === -1) {
|
||||
Tasks.addTagToTask(task._id, tagId);
|
||||
task.tags.push(tagId);
|
||||
} else {
|
||||
Tasks.removeTagFromTask(task._id, tagId);
|
||||
task.tags.splice(tagIndex, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
------------------------
|
||||
Checklists
|
||||
------------------------
|
||||
*/
|
||||
$scope.addChecklist = Tasks.addChecklist;
|
||||
|
||||
$scope.addChecklistItem = Tasks.addChecklistItemToUI;
|
||||
|
||||
$scope.removeChecklistItem = Tasks.removeChecklistItemFromUI;
|
||||
|
||||
$scope.swapChecklistItems = Tasks.swapChecklistItems;
|
||||
|
||||
$scope.navigateChecklist = Tasks.navigateChecklist;
|
||||
|
||||
$scope.checklistCompletion = Tasks.checklistCompletion;
|
||||
|
||||
$scope.collapseChecklist = function (task) {
|
||||
Tasks.collapseChecklist(task);
|
||||
//@TODO: Currently the api save of the task is separate, so whenever we need to save the task we need to call the respective api
|
||||
Tasks.updateTask(task._id, task);
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
(function(){
|
||||
angular
|
||||
.module('habitrpg')
|
||||
.directive('groupTasks', hrpgSortTags);
|
||||
|
||||
hrpgSortTags.$inject = [
|
||||
];
|
||||
|
||||
function hrpgSortTags() {
|
||||
|
||||
return {
|
||||
scope: true,
|
||||
templateUrl: 'partials/groups.tasks.html',
|
||||
controller: 'GroupTasksCtrl',
|
||||
link: function($scope, element, attrs, ngModel) {
|
||||
},
|
||||
};
|
||||
}
|
||||
}());
|
||||
@@ -101,24 +101,8 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
|
||||
};
|
||||
|
||||
$scope.saveTask = function(task, stayOpen, isSaveAndClose) {
|
||||
if (task._edit) {
|
||||
angular.copy(task._edit, task);
|
||||
}
|
||||
task._edit = undefined;
|
||||
|
||||
if (task.checklist) {
|
||||
task.checklist = _.filter(task.checklist, function (i) {
|
||||
return !!i.text
|
||||
});
|
||||
}
|
||||
|
||||
Tasks.saveTask (task, stayOpen, isSaveAndClose);
|
||||
User.updateTask(task, {body: task});
|
||||
if (!stayOpen) task._editing = false;
|
||||
|
||||
if (isSaveAndClose) {
|
||||
$("#task-" + task._id).parent().children('.popover').removeClass('in');
|
||||
}
|
||||
|
||||
if (task.type == 'habit') Guide.goto('intro', 3);
|
||||
};
|
||||
|
||||
@@ -132,8 +116,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
|
||||
$scope.cancelTaskEdit = Tasks.cancelTaskEdit;
|
||||
|
||||
$scope.removeTask = function(task) {
|
||||
if (!confirm(window.env.t('sureDelete', {taskType: window.env.t(task.type), taskText: task.text}))) return;
|
||||
task._edit = undefined;
|
||||
if (!Tasks.removeTask(task)) return;
|
||||
User.deleteTask({params:{id: task._id, taskType: task.type}})
|
||||
};
|
||||
|
||||
@@ -190,60 +173,28 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
|
||||
Checklists
|
||||
------------------------
|
||||
*/
|
||||
function focusChecklist(task,index) {
|
||||
window.setTimeout(function(){
|
||||
$('#task-'+task._id+' .checklist-form input[type="text"]')[index].focus();
|
||||
});
|
||||
}
|
||||
/*
|
||||
------------------------
|
||||
Checklists
|
||||
------------------------
|
||||
*/
|
||||
$scope.addChecklist = Tasks.addChecklist;
|
||||
|
||||
$scope.addChecklist = function(task) {
|
||||
task._edit.checklist = [{completed:false, text:""}];
|
||||
focusChecklist(task._edit,0);
|
||||
}
|
||||
$scope.addChecklistItem = Tasks.addChecklistItemToUI;
|
||||
|
||||
$scope.addChecklistItem = function(task, $event, $index) {
|
||||
if (task._edit.checklist[$index].text) {
|
||||
if ($index === task._edit.checklist.length - 1) {
|
||||
task._edit.checklist.push({ completed: false, text: '' });
|
||||
}
|
||||
focusChecklist(task._edit, $index + 1);
|
||||
} else {
|
||||
// TODO Provide UI feedback that this item is still blank
|
||||
}
|
||||
}
|
||||
$scope.removeChecklistItem = Tasks.removeChecklistItemFromUI;
|
||||
|
||||
$scope.removeChecklistItem = function(task, $event, $index, force) {
|
||||
// Remove item if clicked on trash icon
|
||||
if (force) {
|
||||
task._edit.checklist.splice($index, 1);
|
||||
} else if (!task._edit.checklist[$index].text) {
|
||||
// User deleted all the text and is now wishing to delete the item
|
||||
// saveTask will prune the empty item
|
||||
// Move focus if the list is still non-empty
|
||||
if ($index > 0)
|
||||
focusChecklist(task._edit, $index-1);
|
||||
// Don't allow the backspace key to navigate back now that the field is gone
|
||||
$event.preventDefault();
|
||||
}
|
||||
}
|
||||
$scope.swapChecklistItems = Tasks.swapChecklistItems;
|
||||
|
||||
$scope.swapChecklistItems = function(task, oldIndex, newIndex) {
|
||||
var toSwap = task._edit.checklist.splice(oldIndex, 1)[0];
|
||||
task._edit.checklist.splice(newIndex, 0, toSwap);
|
||||
}
|
||||
$scope.navigateChecklist = Tasks.navigateChecklist;
|
||||
|
||||
$scope.navigateChecklist = function(task,$index,$event){
|
||||
focusChecklist(task, $event.keyCode == '40' ? $index+1 : $index-1);
|
||||
}
|
||||
$scope.checklistCompletion = Tasks.checklistCompletion;
|
||||
|
||||
$scope.checklistCompletion = function(checklist){
|
||||
return _.reduce(checklist,function(m,i){return m+(i.completed ? 1 : 0);},0)
|
||||
}
|
||||
|
||||
$scope.collapseChecklist = function(task) {
|
||||
task.collapseChecklist = !task.collapseChecklist;
|
||||
$scope.saveTask(task,true);
|
||||
}
|
||||
$scope.collapseChecklist = function (task) {
|
||||
Tasks.collapseChecklist(task);
|
||||
//@TODO: Currently the api save of the task is separate, so whenever we need to save the task we need to call the respective api
|
||||
Tasks.updateTask(task._id, task);
|
||||
};
|
||||
|
||||
/*
|
||||
------------------------
|
||||
|
||||
@@ -29,6 +29,33 @@ angular.module('habitrpg')
|
||||
list.focus = true;
|
||||
};
|
||||
|
||||
function removeTask (task) {
|
||||
if (!confirm(window.env.t('sureDelete', {taskType: window.env.t(task.type), taskText: task.text}))) {
|
||||
return false;
|
||||
};
|
||||
task._edit = undefined;
|
||||
return true;
|
||||
}
|
||||
|
||||
function saveTask (task, stayOpen, isSaveAndClose) {
|
||||
if (task._edit) {
|
||||
angular.copy(task._edit, task);
|
||||
}
|
||||
task._edit = undefined;
|
||||
|
||||
if (task.checklist) {
|
||||
task.checklist = _.filter(task.checklist, function (i) {
|
||||
return !!i.text
|
||||
});
|
||||
}
|
||||
|
||||
if (!stayOpen) task._editing = false;
|
||||
|
||||
if (isSaveAndClose) {
|
||||
$("#task-" + task._id).parent().children('.popover').removeClass('in');
|
||||
}
|
||||
}
|
||||
|
||||
function getUserTasks (getCompletedTodos) {
|
||||
var url = '/api/v3/tasks/user';
|
||||
|
||||
@@ -64,6 +91,21 @@ angular.module('habitrpg')
|
||||
});
|
||||
};
|
||||
|
||||
function getGroupTasks (groupId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: '/api/v3/tasks/group/' + groupId,
|
||||
});
|
||||
};
|
||||
|
||||
function createGroupTasks (groupId, taskDetails) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/group/' + groupId,
|
||||
data: taskDetails,
|
||||
});
|
||||
};
|
||||
|
||||
function getTask (taskId) {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
@@ -175,6 +217,20 @@ angular.module('habitrpg')
|
||||
});
|
||||
};
|
||||
|
||||
function assignTask (taskId, userId) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/' + taskId + '/assign/' + userId,
|
||||
});
|
||||
};
|
||||
|
||||
function unAssignTask (taskId, userId) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/' + taskId + '/unassign/' + userId,
|
||||
});
|
||||
};
|
||||
|
||||
function editTask(task, user) {
|
||||
task._editing = true;
|
||||
task._tags = !user.preferences.tagsCollapsed;
|
||||
@@ -211,14 +267,79 @@ angular.module('habitrpg')
|
||||
return cleansedTask;
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------
|
||||
Checklists
|
||||
------------------------
|
||||
*/
|
||||
|
||||
function focusChecklist(task, index) {
|
||||
window.setTimeout(function(){
|
||||
$('#task-'+task._id+' .checklist-form input[type="text"]')[index].focus();
|
||||
});
|
||||
}
|
||||
|
||||
function addChecklist(task) {
|
||||
task._edit.checklist = [{completed:false, text:""}];
|
||||
focusChecklist(task._edit,0);
|
||||
}
|
||||
|
||||
function addChecklistItemToUI(task, $event, $index) {
|
||||
if (task._edit.checklist[$index].text) {
|
||||
if ($index === task._edit.checklist.length - 1) {
|
||||
task._edit.checklist.push({ completed: false, text: '' });
|
||||
}
|
||||
focusChecklist(task._edit, $index + 1);
|
||||
} else {
|
||||
// TODO Provide UI feedback that this item is still blank
|
||||
}
|
||||
}
|
||||
|
||||
function removeChecklistItemFromUI(task, $event, $index, force) {
|
||||
// Remove item if clicked on trash icon
|
||||
if (force) {
|
||||
task._edit.checklist.splice($index, 1);
|
||||
} else if (!task._edit.checklist[$index].text) {
|
||||
// User deleted all the text and is now wishing to delete the item
|
||||
// saveTask will prune the empty item
|
||||
// Move focus if the list is still non-empty
|
||||
if ($index > 0)
|
||||
focusChecklist(task._edit, $index-1);
|
||||
// Don't allow the backspace key to navigate back now that the field is gone
|
||||
$event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function swapChecklistItems(task, oldIndex, newIndex) {
|
||||
var toSwap = task._edit.checklist.splice(oldIndex, 1)[0];
|
||||
task._edit.checklist.splice(newIndex, 0, toSwap);
|
||||
}
|
||||
|
||||
function navigateChecklist(task,$index,$event) {
|
||||
focusChecklist(task, $event.keyCode == '40' ? $index+1 : $index-1);
|
||||
}
|
||||
|
||||
function checklistCompletion(checklist) {
|
||||
return _.reduce(checklist,function(m,i){return m+(i.completed ? 1 : 0);},0)
|
||||
}
|
||||
|
||||
function collapseChecklist(task) {
|
||||
task.collapseChecklist = !task.collapseChecklist;
|
||||
saveTask(task, true);
|
||||
}
|
||||
|
||||
return {
|
||||
addTasks: addTasks,
|
||||
toggleBulk: toggleBulk,
|
||||
getUserTasks: getUserTasks,
|
||||
removeTask: removeTask,
|
||||
saveTask: saveTask,
|
||||
loadedCompletedTodos: false,
|
||||
createUserTasks: createUserTasks,
|
||||
getChallengeTasks: getChallengeTasks,
|
||||
createChallengeTasks: createChallengeTasks,
|
||||
getGroupTasks: getGroupTasks,
|
||||
createGroupTasks: createGroupTasks,
|
||||
getTask: getTask,
|
||||
updateTask: updateTask,
|
||||
deleteTask: deleteTask,
|
||||
@@ -235,6 +356,16 @@ angular.module('habitrpg')
|
||||
clearCompletedTodos: clearCompletedTodos,
|
||||
editTask: editTask,
|
||||
cancelTaskEdit: cancelTaskEdit,
|
||||
cloneTask: cloneTask
|
||||
cloneTask: cloneTask,
|
||||
assignTask: assignTask,
|
||||
unAssignTask: unAssignTask,
|
||||
|
||||
addChecklist: addChecklist,
|
||||
addChecklistItemToUI: addChecklistItemToUI,
|
||||
removeChecklistItemFromUI: removeChecklistItemFromUI,
|
||||
swapChecklistItems: swapChecklistItems,
|
||||
navigateChecklist: navigateChecklist,
|
||||
checklistCompletion: checklistCompletion,
|
||||
collapseChecklist: collapseChecklist,
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -209,5 +209,8 @@
|
||||
"exportInboxPopoverBody": "HTML allows easy reading of messages in a browser. For a machine-readable format, use Data > Export Data",
|
||||
"to": "To:",
|
||||
"from": "From:",
|
||||
"desktopNotificationsText": "We need your permission to enable desktop notifications for new messages in party chat! Follow your browser's instructions to turn them on.<br><br>You'll receive these notifications only while you have Habitica open. If you decide you don't like them, they can be disabled in your browser's settings.<br><br>This box will close automatically when a decision is made."
|
||||
"desktopNotificationsText": "We need your permission to enable desktop notifications for new messages in party chat! Follow your browser's instructions to turn them on.<br><br>You'll receive these notifications only while you have Habitica open. If you decide you don't like them, they can be disabled in your browser's settings.<br><br>This box will close automatically when a decision is made.",
|
||||
"confirmAddTag": "Do you really want to add \"<%= tag %>\"?",
|
||||
"confirmRemoveTag": "Do you really want to remove \"<%= tag %>\"?",
|
||||
"assignTask": "Assign Task"
|
||||
}
|
||||
|
||||
@@ -259,6 +259,8 @@ api.updateTask = {
|
||||
|
||||
if (challenge) {
|
||||
challenge.updateTask(savedTask);
|
||||
} else if (group && task.group.id && task.group.assignedUsers.length > 0) {
|
||||
await group.updateTask(savedTask);
|
||||
} else {
|
||||
taskActivityWebhook.send(user.webhooks, {
|
||||
type: 'updated',
|
||||
@@ -435,6 +437,7 @@ api.addChecklistItem = {
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
let group;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty();
|
||||
|
||||
@@ -446,6 +449,10 @@ api.addChecklistItem = {
|
||||
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
} else if (task.group.id && !task.userId) {
|
||||
group = await Group.getGroup({user, groupId: task.group.id, fields: requiredGroupFields});
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
if (group.leader !== user._id) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
} else if (task.challenge.id && !task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
challenge = await Challenge.findOne({_id: task.challenge.id}).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
@@ -461,6 +468,9 @@ api.addChecklistItem = {
|
||||
|
||||
res.respond(200, savedTask);
|
||||
if (challenge) challenge.updateTask(savedTask);
|
||||
if (group && task.group.id && task.group.assignedUsers.length > 0) {
|
||||
await group.updateTask(savedTask);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -522,6 +532,7 @@ api.updateChecklistItem = {
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
let group;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty();
|
||||
req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID();
|
||||
@@ -534,6 +545,10 @@ api.updateChecklistItem = {
|
||||
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
} else if (task.group.id && !task.userId) {
|
||||
group = await Group.getGroup({user, groupId: task.group.id, fields: requiredGroupFields});
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
if (group.leader !== user._id) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
} else if (task.challenge.id && !task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
challenge = await Challenge.findOne({_id: task.challenge.id}).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
@@ -551,6 +566,9 @@ api.updateChecklistItem = {
|
||||
|
||||
res.respond(200, savedTask);
|
||||
if (challenge) challenge.updateTask(savedTask);
|
||||
if (group && task.group.id && task.group.assignedUsers.length > 0) {
|
||||
await group.updateTask(savedTask);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -572,6 +590,7 @@ api.removeChecklistItem = {
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
let group;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty();
|
||||
req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID();
|
||||
@@ -584,6 +603,10 @@ api.removeChecklistItem = {
|
||||
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
} else if (task.group.id && !task.userId) {
|
||||
group = await Group.getGroup({user, groupId: task.group.id, fields: requiredGroupFields});
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
if (group.leader !== user._id) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
} else if (task.challenge.id && !task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
challenge = await Challenge.findOne({_id: task.challenge.id}).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
@@ -599,6 +622,9 @@ api.removeChecklistItem = {
|
||||
let savedTask = await task.save();
|
||||
res.respond(200, savedTask);
|
||||
if (challenge) challenge.updateTask(savedTask);
|
||||
if (group && task.group.id && task.group.assignedUsers.length > 0) {
|
||||
await group.updateTask(savedTask);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -90,13 +90,16 @@ export let schema = new Schema({
|
||||
todos: [{type: String, ref: 'Task'}],
|
||||
rewards: [{type: String, ref: 'Task'}],
|
||||
},
|
||||
purchased: {
|
||||
active: {type: Boolean, default: false},
|
||||
},
|
||||
}, {
|
||||
strict: true,
|
||||
minimize: false, // So empty objects are returned
|
||||
});
|
||||
|
||||
schema.plugin(baseModel, {
|
||||
noSet: ['_id', 'balance', 'quest', 'memberCount', 'chat', 'challengeCount', 'tasksOrder'],
|
||||
noSet: ['_id', 'balance', 'quest', 'memberCount', 'chat', 'challengeCount', 'tasksOrder', 'purchased'],
|
||||
});
|
||||
|
||||
// A list of additional fields that cannot be updated (but can be set on creation)
|
||||
|
||||
@@ -147,7 +147,20 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
|
||||
h3.popover-title {{group.leader.profile.name}}
|
||||
.popover-content
|
||||
markdown(text='group._editing ? groupCopy.leaderMessage : group.leaderMessage')
|
||||
div(ng-controller='ChatCtrl')
|
||||
|
||||
ul.options-menu(ng-init="groupPane = 'chat'", ng-show="group.purchased.active")
|
||||
li
|
||||
a(ng-click="groupPane = 'chat'")
|
||||
| Chat
|
||||
li
|
||||
a(ng-click="groupPane = 'tasks'")
|
||||
| Tasks
|
||||
|
||||
|
||||
.tab-content
|
||||
.tab-pane.active
|
||||
|
||||
div(ng-controller='ChatCtrl', ng-show="groupPane == 'chat'")
|
||||
.alert.alert-info.alert-sm(ng-if='group.memberCount > Shared.constants.LARGE_GROUP_COUNT_MESSAGE_CUTOFF')=env.t('largeGroupNote')
|
||||
h3=env.t('chat')
|
||||
include ./chat-box
|
||||
@@ -155,3 +168,5 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
|
||||
+chatMessages()
|
||||
h4(ng-if='group.chat.length < 1 && group.type === "party"')=env.t('partyChatEmpty')
|
||||
h4(ng-if='group.chat.length < 1 && group.type === "guild"')=env.t('guildChatEmpty')
|
||||
|
||||
group-tasks(ng-show="groupPane == 'tasks'")
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
script(type='text/ng-template', id='partials/groups.members.autocomplete.html')
|
||||
div#taggle.input.textarea.clearfix
|
||||
@@ -0,0 +1,4 @@
|
||||
script(type='text/ng-template', id='partials/groups.tasks.actions.html')
|
||||
div(ng-if="group.leader._id === user._id", class="col-md-12")
|
||||
strong=env.t('assignTask')
|
||||
group-members-autocomplete(ng-model="assignedMembers")
|
||||
5
website/views/options/social/groups/group-tasks.jade
Normal file
5
website/views/options/social/groups/group-tasks.jade
Normal file
@@ -0,0 +1,5 @@
|
||||
include ./group-tasks-actions
|
||||
include ./group-members-autocomplete
|
||||
|
||||
script(type='text/ng-template', id='partials/groups.tasks.html')
|
||||
habitrpg-tasks(main=false)
|
||||
@@ -6,6 +6,7 @@ include ./hall
|
||||
include ./quests/index
|
||||
include ./chat-message
|
||||
include ./party
|
||||
include ./groups/group-tasks
|
||||
|
||||
script(type='text/ng-template', id='partials/options.social.inbox.html')
|
||||
.options-blurbmenu
|
||||
|
||||
@@ -9,6 +9,9 @@ div(ng-if='::task.type!="reward"')
|
||||
a.hint(href='http://habitica.wikia.com/wiki/Task_Alias', target='_blank', popover-trigger='mouseenter', popover="{{::env.t('taskAliasPopover')}} {{::task._edit.alias ? '\n\n\' + env.t('taskAliasPopoverWarning') : ''}}")=env.t('taskAlias')
|
||||
input.form-control(ng-model='task._edit.alias' type='text' placeholder=env.t('taskAliasPlaceholder'))
|
||||
|
||||
fieldset.option-group.advanced-option(ng-show="task._edit._advanced")
|
||||
group-tasks-actions(ng-if="obj.type == 'guild'", task='task', group='obj')
|
||||
|
||||
div(ng-show='task._edit._advanced')
|
||||
div(ng-if='::task.type == "daily"')
|
||||
.form-group
|
||||
|
||||
@@ -6,7 +6,7 @@ include ./task_view/mixins
|
||||
script(id='templates/habitrpg-tasks.html', type="text/ng-template")
|
||||
.tasks-lists.container-fluid
|
||||
.row
|
||||
.col-md-3.col-sm-6(ng-repeat='list in lists', ng-class='::{ "rewards-module": list.type==="reward", "new-row-md": list.type==="todo" }')
|
||||
.col-md-3.col-sm-6(ng-repeat='list in lists', ng-class='::{ "rewards-module": list.type==="reward", "new-row-md": list.type==="todo", "col-md-2": obj.type }')
|
||||
.task-column(class='{{::list.type}}s')
|
||||
|
||||
include ./task_view/graph
|
||||
|
||||
Reference in New Issue
Block a user