mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 07:20:12 +01:00
Assignee field like Jira #2452 , in progress.
Assignee can not be removed yet, it removes member, wrong link in popup. Thanks to xet7 !
This commit is contained in:
parent
3f19091a91
commit
9e1aaf163f
8 changed files with 258 additions and 5 deletions
|
|
@ -73,6 +73,15 @@ template(name="cardDetails")
|
|||
a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
|
||||
i.fa.fa-plus
|
||||
|
||||
.card-details-item.card-details-item-assignees
|
||||
h3.card-details-item-title {{_ 'assignee'}}
|
||||
each getAssignees
|
||||
+userAvatar(userId=this cardId=../_id)
|
||||
| {{! XXX Hack to hide syntaxic coloration /// }}
|
||||
if canModifyCard
|
||||
a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
|
||||
i.fa.fa-plus
|
||||
|
||||
.card-details-item.card-details-item-labels
|
||||
h3.card-details-item-title {{_ 'labels'}}
|
||||
a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
|
||||
|
|
@ -296,6 +305,18 @@ template(name="cardMembersPopup")
|
|||
if isCardMember
|
||||
i.fa.fa-check
|
||||
|
||||
template(name="cardAssigneesPopup")
|
||||
ul.pop-over-list.js-card-assignee-list
|
||||
each board.activeAssignees
|
||||
li.item(class="{{#if isCardAssignee}}active{{/if}}")
|
||||
a.name.js-select-assignee(href="#")
|
||||
+userAvatarAssignee(userId=user._id)
|
||||
span.full-name
|
||||
= user.profile.fullname
|
||||
| (<span class="username">{{ user.username }}</span>)
|
||||
if isCardAssignee
|
||||
i.fa.fa-check
|
||||
|
||||
template(name="cardMorePopup")
|
||||
p.quiet
|
||||
span.clearfix
|
||||
|
|
|
|||
|
|
@ -309,6 +309,8 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
'click .js-member': Popup.open('cardMember'),
|
||||
'click .js-add-members': Popup.open('cardMembers'),
|
||||
'click .js-assignee': Popup.open('cardAssignee'),
|
||||
'click .js-add-assignees': Popup.open('cardAssignees'),
|
||||
'click .js-add-labels': Popup.open('cardLabels'),
|
||||
'click .js-received-date': Popup.open('editCardReceivedDate'),
|
||||
'click .js-start-date': Popup.open('editCardStartDate'),
|
||||
|
|
@ -399,6 +401,7 @@ Template.cardDetailsActionsPopup.helpers({
|
|||
|
||||
Template.cardDetailsActionsPopup.events({
|
||||
'click .js-members': Popup.open('cardMembers'),
|
||||
'click .js-assignees': Popup.open('cardAssignees'),
|
||||
'click .js-labels': Popup.open('cardLabels'),
|
||||
'click .js-attachments': Popup.open('cardAttachments'),
|
||||
'click .js-custom-fields': Popup.open('cardCustomFields'),
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@
|
|||
margin-right: 0
|
||||
&.card-details-item-labels,
|
||||
&.card-details-item-members,
|
||||
&.card-details-item-assignees,
|
||||
&.card-details-item-received,
|
||||
&.card-details-item-start,
|
||||
&.card-details-item-due,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,23 @@ template(name="userAvatar")
|
|||
a.edit-avatar.js-change-avatar
|
||||
i.fa.fa-pencil
|
||||
|
||||
template(name="userAvatarAssignee")
|
||||
a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})")
|
||||
if userData.profile.avatarUrl
|
||||
img.avatar.avatar-image(src="{{userData.profile.avatarUrl}}")
|
||||
else
|
||||
+userAvatarInitials(userId=userData._id)
|
||||
|
||||
if showStatus
|
||||
span.assignee-presence-status(class=presenceStatusClassName)
|
||||
span.assignee-type(class=assigneeType)
|
||||
|
||||
unless isSandstorm
|
||||
if showEdit
|
||||
if $eq currentUser._id userData._id
|
||||
a.edit-avatar.js-change-avatar
|
||||
i.fa.fa-pencil
|
||||
|
||||
template(name="userAvatarInitials")
|
||||
svg.avatar.avatar-initials(viewBox="0 0 {{viewPortWidth}} 15")
|
||||
text(x="50%" y="13" text-anchor="middle")= initials
|
||||
|
|
@ -78,3 +95,18 @@ template(name="cardMemberPopup")
|
|||
if $eq currentUser._id user._id
|
||||
with currentUser
|
||||
li: a.js-edit-profile {{_ 'edit-profile'}}
|
||||
|
||||
template(name="cardAssigneePopup")
|
||||
.board-assignee-menu
|
||||
.mini-profile-info
|
||||
+userAvatar(userId=user._id showEdit=true)
|
||||
.info
|
||||
h3= user.profile.fullname
|
||||
p.quiet @{{ user.username }}
|
||||
ul.pop-over-list
|
||||
if currentUser.isNotCommentOnly
|
||||
li: a.js-remove-assignee {{_ 'remove-member-from-card'}}
|
||||
|
||||
if $eq currentUser._id user._id
|
||||
with currentUser
|
||||
li: a.js-edit-profile {{_ 'edit-profile'}}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,13 @@ Template.cardMembersPopup.helpers({
|
|||
return _.contains(cardMembers, this.userId);
|
||||
},
|
||||
|
||||
isCardAssignee() {
|
||||
const card = Template.parentData();
|
||||
const cardAssignees = card.getAssignees();
|
||||
|
||||
return _.contains(cardAssignees, this.userId);
|
||||
},
|
||||
|
||||
user() {
|
||||
return Users.findOne(this.userId);
|
||||
},
|
||||
|
|
@ -166,3 +173,26 @@ Template.cardMemberPopup.events({
|
|||
},
|
||||
'click .js-edit-profile': Popup.open('editProfile'),
|
||||
});
|
||||
|
||||
Template.cardAssigneesPopup.events({
|
||||
'click .js-select-assignee'(event) {
|
||||
const card = Cards.findOne(Session.get('currentCard'));
|
||||
const assigneeId = this.userId;
|
||||
card.toggleAssignee(assigneeId);
|
||||
event.preventDefault();
|
||||
},
|
||||
});
|
||||
|
||||
Template.cardAssigneePopup.helpers({
|
||||
user() {
|
||||
return Users.findOne(this.userId);
|
||||
},
|
||||
});
|
||||
|
||||
Template.cardAssigneePopup.events({
|
||||
'click .js-remove-assignee'() {
|
||||
Cards.findOne(this.cardId).unassignAssignee(this.userId);
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-edit-profile': Popup.open('editProfile'),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
avatar-radius = 50%
|
||||
|
||||
.member
|
||||
.member,
|
||||
.assignee
|
||||
border-radius: 3px
|
||||
display: block
|
||||
position: relative
|
||||
|
|
@ -32,7 +33,8 @@ avatar-radius = 50%
|
|||
height: 100%
|
||||
width: @height
|
||||
|
||||
.member-presence-status
|
||||
.member-presence-status,
|
||||
.assignee-presence-status
|
||||
background-color: #b3b3b3
|
||||
border: 1px solid #fff
|
||||
border-radius: 50%
|
||||
|
|
@ -79,7 +81,8 @@ avatar-radius = 50%
|
|||
color: white
|
||||
|
||||
|
||||
&.add-member
|
||||
&.add-member,
|
||||
&.add-assignee
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
|
@ -111,7 +114,8 @@ avatar-radius = 50%
|
|||
p
|
||||
padding-top: 0
|
||||
|
||||
.member
|
||||
.member,
|
||||
.assignee
|
||||
width: 50px
|
||||
height: @width
|
||||
margin-right: 10px
|
||||
|
|
|
|||
|
|
@ -750,5 +750,6 @@
|
|||
"delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.",
|
||||
"accounts-allowUserDelete": "Allow users to self delete their account",
|
||||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles"
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee"
|
||||
}
|
||||
|
|
|
|||
161
models/cards.js
161
models/cards.js
|
|
@ -203,6 +203,14 @@ Cards.attachSchema(
|
|||
optional: true,
|
||||
defaultValue: [],
|
||||
},
|
||||
assignees: {
|
||||
/**
|
||||
* who assignees of the card (user IDs)
|
||||
*/
|
||||
type: [String],
|
||||
optional: true,
|
||||
defaultValue: [],
|
||||
},
|
||||
receivedAt: {
|
||||
/**
|
||||
* Date the card was received
|
||||
|
|
@ -411,6 +419,10 @@ Cards.helpers({
|
|||
return _.contains(this.getMembers(), memberId);
|
||||
},
|
||||
|
||||
isAssignee(assigneeId) {
|
||||
return _.contains(this.getAssignees(), assigneeId);
|
||||
},
|
||||
|
||||
activities() {
|
||||
if (this.isLinkedCard()) {
|
||||
return Activities.find(
|
||||
|
|
@ -745,6 +757,20 @@ Cards.helpers({
|
|||
}
|
||||
},
|
||||
|
||||
getAssignees() {
|
||||
if (this.isLinkedCard()) {
|
||||
const card = Cards.findOne({ _id: this.linkedId });
|
||||
return card.assignees;
|
||||
} else if (this.isLinkedBoard()) {
|
||||
const board = Boards.findOne({ _id: this.linkedId });
|
||||
return board.activeAssignees().map(assignee => {
|
||||
return assignee.userId;
|
||||
});
|
||||
} else {
|
||||
return this.assignees;
|
||||
}
|
||||
},
|
||||
|
||||
assignMember(memberId) {
|
||||
if (this.isLinkedCard()) {
|
||||
return Cards.update(
|
||||
|
|
@ -762,6 +788,23 @@ Cards.helpers({
|
|||
}
|
||||
},
|
||||
|
||||
assignAssignee(assigneeId) {
|
||||
if (this.isLinkedCard()) {
|
||||
return Cards.update(
|
||||
{ _id: this.linkedId },
|
||||
{ $addToSet: { assignees: assigneeId } },
|
||||
);
|
||||
} else if (this.isLinkedBoard()) {
|
||||
const board = Boards.findOne({ _id: this.linkedId });
|
||||
return board.addAssignee(assigneeId);
|
||||
} else {
|
||||
return Cards.update(
|
||||
{ _id: this._id },
|
||||
{ $addToSet: { assignees: assigneeId } },
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
unassignMember(memberId) {
|
||||
if (this.isLinkedCard()) {
|
||||
return Cards.update(
|
||||
|
|
@ -776,6 +819,23 @@ Cards.helpers({
|
|||
}
|
||||
},
|
||||
|
||||
unassignAssignee(assigneeId) {
|
||||
if (this.isLinkedCard()) {
|
||||
return Cards.update(
|
||||
{ _id: this.linkedId },
|
||||
{ $pull: { assignees: assigneeId } },
|
||||
);
|
||||
} else if (this.isLinkedBoard()) {
|
||||
const board = Boards.findOne({ _id: this.linkedId });
|
||||
return board.removeAssignee(assigneeId);
|
||||
} else {
|
||||
return Cards.update(
|
||||
{ _id: this._id },
|
||||
{ $pull: { assignees: assigneeId } },
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
toggleMember(memberId) {
|
||||
if (this.getMembers() && this.getMembers().indexOf(memberId) > -1) {
|
||||
return this.unassignMember(memberId);
|
||||
|
|
@ -784,6 +844,14 @@ Cards.helpers({
|
|||
}
|
||||
},
|
||||
|
||||
toggleAssignee(assigneeId) {
|
||||
if (this.getAssignees() && this.getAssignees().indexOf(assigneeId) > -1) {
|
||||
return this.unassignAssignee(assigneeId);
|
||||
} else {
|
||||
return this.assignAssignee(assigneeId);
|
||||
}
|
||||
},
|
||||
|
||||
getReceived() {
|
||||
if (this.isLinkedCard()) {
|
||||
const card = Cards.findOne({ _id: this.linkedId });
|
||||
|
|
@ -1126,6 +1194,14 @@ Cards.mutations({
|
|||
};
|
||||
},
|
||||
|
||||
assignAssignee(assigneeId) {
|
||||
return {
|
||||
$addToSet: {
|
||||
assignees: assigneeId,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
unassignMember(memberId) {
|
||||
return {
|
||||
$pull: {
|
||||
|
|
@ -1134,6 +1210,14 @@ Cards.mutations({
|
|||
};
|
||||
},
|
||||
|
||||
unassignAssignee(assigneeId) {
|
||||
return {
|
||||
$pull: {
|
||||
assignees: assigneeId,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
toggleMember(memberId) {
|
||||
if (this.members && this.members.indexOf(memberId) > -1) {
|
||||
return this.unassignMember(memberId);
|
||||
|
|
@ -1142,6 +1226,14 @@ Cards.mutations({
|
|||
}
|
||||
},
|
||||
|
||||
toggleAssignee(assigneeId) {
|
||||
if (this.assignees && this.assignees.indexOf(assigneeId) > -1) {
|
||||
return this.unassignAssignee(assigneeId);
|
||||
} else {
|
||||
return this.assignAssignee(assigneeId);
|
||||
}
|
||||
},
|
||||
|
||||
assignCustomField(customFieldId) {
|
||||
return {
|
||||
$addToSet: {
|
||||
|
|
@ -1436,6 +1528,46 @@ function cardMembers(userId, doc, fieldNames, modifier) {
|
|||
}
|
||||
}
|
||||
|
||||
function cardAssignees(userId, doc, fieldNames, modifier) {
|
||||
if (!_.contains(fieldNames, 'assignees')) return;
|
||||
let assigneeId;
|
||||
// Say hello to the new assignee
|
||||
if (modifier.$addToSet && modifier.$addToSet.assignees) {
|
||||
assigneeId = modifier.$addToSet.assignees;
|
||||
const username = Users.findOne(assigneeId).username;
|
||||
if (!_.contains(doc.assignees, assigneeId)) {
|
||||
Activities.insert({
|
||||
userId,
|
||||
username,
|
||||
activityType: 'joinAssignee',
|
||||
boardId: doc.boardId,
|
||||
cardId: doc._id,
|
||||
assigneeId,
|
||||
listId: doc.listId,
|
||||
swimlaneId: doc.swimlaneId,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Say goodbye to the former assignee
|
||||
if (modifier.$pull && modifier.$pull.assignees) {
|
||||
assigneeId = modifier.$pull.assignees;
|
||||
const username = Users.findOne(assigneeId).username;
|
||||
// Check that the former assignee is assignee of the card
|
||||
if (_.contains(doc.assignees, assigneeId)) {
|
||||
Activities.insert({
|
||||
userId,
|
||||
username,
|
||||
activityType: 'unjoinAssignee',
|
||||
boardId: doc.boardId,
|
||||
cardId: doc._id,
|
||||
assigneeId,
|
||||
listId: doc.listId,
|
||||
swimlaneId: doc.swimlaneId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cardLabels(userId, doc, fieldNames, modifier) {
|
||||
if (!_.contains(fieldNames, 'labelIds')) return;
|
||||
let labelId;
|
||||
|
|
@ -1673,6 +1805,12 @@ if (Meteor.isServer) {
|
|||
updateActivities(doc, fieldNames, modifier);
|
||||
});
|
||||
|
||||
// Add a new activity if we add or remove a assignee to the card
|
||||
Cards.before.update((userId, doc, fieldNames, modifier) => {
|
||||
cardAssignees(userId, doc, fieldNames, modifier);
|
||||
updateActivities(doc, fieldNames, modifier);
|
||||
});
|
||||
|
||||
// Add a new activity if we add or remove a label to the card
|
||||
Cards.before.update((userId, doc, fieldNames, modifier) => {
|
||||
cardLabels(userId, doc, fieldNames, modifier);
|
||||
|
|
@ -1852,6 +1990,7 @@ if (Meteor.isServer) {
|
|||
* @param {string} description the description of the new card
|
||||
* @param {string} swimlaneId the swimlane ID of the new card
|
||||
* @param {string} [members] the member IDs list of the new card
|
||||
* @param {string} [assignees] the assignee IDs list of the new card
|
||||
* @return_type {_id: string}
|
||||
*/
|
||||
JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function(
|
||||
|
|
@ -1873,6 +2012,7 @@ if (Meteor.isServer) {
|
|||
_id: req.body.authorId,
|
||||
});
|
||||
const members = req.body.members || [req.body.authorId];
|
||||
const assignees = req.body.assignees;
|
||||
if (typeof check !== 'undefined') {
|
||||
const id = Cards.direct.insert({
|
||||
title: req.body.title,
|
||||
|
|
@ -1884,6 +2024,7 @@ if (Meteor.isServer) {
|
|||
swimlaneId: req.body.swimlaneId,
|
||||
sort: currentCards.count(),
|
||||
members,
|
||||
assignees,
|
||||
});
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
|
|
@ -1935,6 +2076,7 @@ if (Meteor.isServer) {
|
|||
* @param {string} [labelIds] the new list of label IDs attached to the card
|
||||
* @param {string} [swimlaneId] the new swimlane ID of the card
|
||||
* @param {string} [members] the new list of member IDs attached to the card
|
||||
* @param {string} [assignees] the new list of assignee IDs attached to the card
|
||||
* @param {string} [requestedBy] the new requestedBy field of the card
|
||||
* @param {string} [assignedBy] the new assignedBy field of the card
|
||||
* @param {string} [receivedAt] the new receivedAt field of the card
|
||||
|
|
@ -2195,6 +2337,25 @@ if (Meteor.isServer) {
|
|||
{ $set: { members: newmembers } },
|
||||
);
|
||||
}
|
||||
if (req.body.hasOwnProperty('assignees')) {
|
||||
let newassignees = req.body.assignees;
|
||||
if (_.isString(newassignees)) {
|
||||
if (newassignees === '') {
|
||||
newassignees = null;
|
||||
} else {
|
||||
newassignees = [newassignees];
|
||||
}
|
||||
}
|
||||
Cards.direct.update(
|
||||
{
|
||||
_id: paramCardId,
|
||||
listId: paramListId,
|
||||
boardId: paramBoardId,
|
||||
archived: false,
|
||||
},
|
||||
{ $set: { assignees: newassignees } },
|
||||
);
|
||||
}
|
||||
if (req.body.hasOwnProperty('swimlaneId')) {
|
||||
const newParamSwimlaneId = req.body.swimlaneId;
|
||||
Cards.direct.update(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue