summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
authorLauri Ojansivu <x@xet7.org>2019-11-02 16:12:40 +0200
committerLauri Ojansivu <x@xet7.org>2019-11-02 16:12:40 +0200
commit3e8f9ef1a5275a5e9b691c7e74dc73b97a43689a (patch)
treee035c173de03a19ba61e718035609d1881edfd0c /client
parent92efb8bec4744d7eacb134109a325a2c960790cb (diff)
downloadwekan-3e8f9ef1a5275a5e9b691c7e74dc73b97a43689a.tar.gz
wekan-3e8f9ef1a5275a5e9b691c7e74dc73b97a43689a.tar.bz2
wekan-3e8f9ef1a5275a5e9b691c7e74dc73b97a43689a.zip
Assignee field like Jira #2452 , in progress.
Added features: - Assignee can now be added and removed. - Avatar icon is at card and assignee details TODO: - When selecting new assignee (+) icon, list does not yet show avatars and names who to add. There is empty avatar without name. Thanks to xet7 !
Diffstat (limited to 'client')
-rw-r--r--client/components/cards/cardDetails.jade40
-rw-r--r--client/components/cards/cardDetails.js104
-rw-r--r--client/components/cards/cardDetails.styl120
-rw-r--r--client/components/users/userAvatar.jade32
-rw-r--r--client/components/users/userAvatar.js30
-rw-r--r--client/components/users/userAvatar.styl12
6 files changed, 266 insertions, 72 deletions
diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade
index 639c7742..ad8010e4 100644
--- a/client/components/cards/cardDetails.jade
+++ b/client/components/cards/cardDetails.jade
@@ -76,7 +76,7 @@ template(name="cardDetails")
.card-details-item.card-details-item-assignees
h3.card-details-item-title {{_ 'assignee'}}
each getAssignees
- +userAvatar(userId=this cardId=../_id)
+ +userAvatarAssignee(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'}}")
@@ -307,7 +307,7 @@ template(name="cardMembersPopup")
template(name="cardAssigneesPopup")
ul.pop-over-list.js-card-assignee-list
- each board.activeAssignees
+ each board.activeMembers
li.item(class="{{#if isCardAssignee}}active{{/if}}")
a.name.js-select-assignee(href="#")
+userAvatarAssignee(userId=user._id)
@@ -317,6 +317,42 @@ template(name="cardAssigneesPopup")
if isCardAssignee
i.fa.fa-check
+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
+ +userAvatarAssigneeInitials(userId=userData._id)
+
+ if showStatus
+ span.member-presence-status(class=presenceStatusClassName)
+ span.member-type(class=memberType)
+
+ unless isSandstorm
+ if showEdit
+ if $eq currentUser._id userData._id
+ a.edit-avatar.js-change-avatar
+ i.fa.fa-pencil
+
+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'}}
+
+template(name="userAvatarAssigneeInitials")
+ svg.avatar.avatar-assignee-initials(viewBox="0 0 {{viewPortWidth}} 15")
+ text(x="50%" y="13" text-anchor="middle")= initials
+
template(name="cardMorePopup")
p.quiet
span.clearfix
diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js
index 6408db74..3b2873a2 100644
--- a/client/components/cards/cardDetails.js
+++ b/client/components/cards/cardDetails.js
@@ -344,6 +344,50 @@ BlazeComponent.extendComponent({
},
}).register('cardDetails');
+Template.cardDetails.helpers({
+ userData() {
+ // We need to handle a special case for the search results provided by the
+ // `matteodem:easy-search` package. Since these results gets published in a
+ // separate collection, and not in the standard Meteor.Users collection as
+ // expected, we use a component parameter ("property") to distinguish the
+ // two cases.
+ const userCollection = this.esSearch ? ESSearchResults : Users;
+ return userCollection.findOne(this.userId, {
+ fields: {
+ profile: 1,
+ username: 1,
+ },
+ });
+ },
+
+ memberType() {
+ const user = Users.findOne(this.userId);
+ return user && user.isBoardAdmin() ? 'admin' : 'normal';
+ },
+
+ presenceStatusClassName() {
+ const user = Users.findOne(this.userId);
+ const userPresence = presences.findOne({ userId: this.userId });
+ if (user && user.isInvitedTo(Session.get('currentBoard'))) return 'pending';
+ else if (!userPresence) return 'disconnected';
+ else if (Session.equals('currentBoard', userPresence.state.currentBoardId))
+ return 'active';
+ else return 'idle';
+ },
+});
+
+Template.userAvatarAssigneeInitials.helpers({
+ initials() {
+ const user = Users.findOne(this.userId);
+ return user && user.getInitials();
+ },
+
+ viewPortWidth() {
+ const user = Users.findOne(this.userId);
+ return ((user && user.getInitials().length) || 1) * 12;
+ },
+});
+
// We extends the normal InlinedForm component to support UnsavedEdits draft
// feature.
(class extends InlinedForm {
@@ -809,3 +853,63 @@ EscapeActions.register(
noClickEscapeOn: '.js-card-details,.board-sidebar,#header',
},
);
+
+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({
+ userData() {
+ // We need to handle a special case for the search results provided by the
+ // `matteodem:easy-search` package. Since these results gets published in a
+ // separate collection, and not in the standard Meteor.Users collection as
+ // expected, we use a component parameter ("property") to distinguish the
+ // two cases.
+ const userCollection = this.esSearch ? ESSearchResults : Users;
+ return userCollection.findOne(this.userId, {
+ fields: {
+ profile: 1,
+ username: 1,
+ },
+ });
+ },
+
+ memberType() {
+ const user = Users.findOne(this.userId);
+ return user && user.isBoardAdmin() ? 'admin' : 'normal';
+ },
+
+ presenceStatusClassName() {
+ const user = Users.findOne(this.userId);
+ const userPresence = presences.findOne({ userId: this.userId });
+ if (user && user.isInvitedTo(Session.get('currentBoard'))) return 'pending';
+ else if (!userPresence) return 'disconnected';
+ else if (Session.equals('currentBoard', userPresence.state.currentBoardId))
+ return 'active';
+ else return 'idle';
+ },
+
+ isCardAssignee() {
+ const card = Template.parentData();
+ const cardAssignees = card.getAssignees();
+
+ return _.contains(cardAssignees, this.userId);
+ },
+
+ 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'),
+});
diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl
index 825e22e9..295a659d 100644
--- a/client/components/cards/cardDetails.styl
+++ b/client/components/cards/cardDetails.styl
@@ -1,5 +1,125 @@
@import 'nib'
+// Assignee, code copied from wekan/client/users/userAvatar.styl
+
+avatar-radius = 50%
+
+.assignee
+ border-radius: 3px
+ display: block
+ position: relative
+ float: left
+ height: 30px
+ width: @height
+ margin: 0 4px 4px 0
+ cursor: pointer
+ user-select: none
+ z-index: 1
+ text-decoration: none
+ border-radius: avatar-radius
+
+ .avatar
+ overflow: hidden
+ border-radius: avatar-radius
+
+ &.avatar-assignee-initials
+ height: 70%
+ width: @height
+ padding: 15%
+ background-color: #dbdbdb
+ color: #444444
+ position: absolute
+
+ &.avatar-image
+ height: 100%
+ width: @height
+
+ .assignee-presence-status
+ background-color: #b3b3b3
+ border: 1px solid #fff
+ border-radius: 50%
+ height: 7px
+ width: @height
+ position: absolute
+ right: -1px
+ bottom: -1px
+ border: 1px solid white
+ z-index: 15
+
+ &.active
+ background: #64c464
+ border-color: #daf1da
+
+ &.idle
+ background: #e4e467
+ border-color: #f7f7d4
+
+ &.disconnected
+ background: #bdbdbd
+ border-color: #ededed
+
+ &.pending
+ background: #e44242
+ border-color: #f1dada
+
+ .edit-avatar
+ position: absolute
+ top: 0
+ height: 100%
+ width: 100%
+ border-radius: avatar-radius
+ background: black
+ display: flex
+ align-items: center
+ justify-content: center
+ opacity: 0
+
+ &:hover
+ opacity: 0.6
+
+ i.fa-pencil
+ color: white
+
+
+ &.add-assignee
+ display: flex
+ align-items: center
+ justify-content: center
+ box-shadow: 0 0 0 2px darken(white, 25%) inset
+
+ &:hover, &.is-active
+ box-shadow: 0 0 0 2px darken(white, 60%) inset
+
+.atMention
+ background: #dbdbdb
+ border-radius: 3px
+ padding: 1px 4px
+ margin: -1px 0
+ display: inline-block
+
+ &.me
+ background: #cfdfe8
+
+.mini-profile-info
+ margin-top: 10px
+
+ .info
+ padding-top: 5px
+
+ h3, p
+ margin-bottom: 0
+ padding-left: 0
+
+ p
+ padding-top: 0
+
+ .assignee
+ width: 50px
+ height: @width
+ margin-right: 10px
+
+// Other card details
+
.card-details
padding: 0
flex-shrink: 0
diff --git a/client/components/users/userAvatar.jade b/client/components/users/userAvatar.jade
index e551cab5..ebfa48ba 100644
--- a/client/components/users/userAvatar.jade
+++ b/client/components/users/userAvatar.jade
@@ -15,23 +15,6 @@ 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
@@ -95,18 +78,3 @@ 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'}}
diff --git a/client/components/users/userAvatar.js b/client/components/users/userAvatar.js
index 7a2831b2..262a63af 100644
--- a/client/components/users/userAvatar.js
+++ b/client/components/users/userAvatar.js
@@ -139,13 +139,6 @@ 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);
},
@@ -173,26 +166,3 @@ 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'),
-});
diff --git a/client/components/users/userAvatar.styl b/client/components/users/userAvatar.styl
index 5fcd9f6c..b962b01c 100644
--- a/client/components/users/userAvatar.styl
+++ b/client/components/users/userAvatar.styl
@@ -2,8 +2,7 @@
avatar-radius = 50%
-.member,
-.assignee
+.member
border-radius: 3px
display: block
position: relative
@@ -33,8 +32,7 @@ avatar-radius = 50%
height: 100%
width: @height
- .member-presence-status,
- .assignee-presence-status
+ .member-presence-status
background-color: #b3b3b3
border: 1px solid #fff
border-radius: 50%
@@ -81,8 +79,7 @@ avatar-radius = 50%
color: white
- &.add-member,
- &.add-assignee
+ &.add-member
display: flex
align-items: center
justify-content: center
@@ -114,8 +111,7 @@ avatar-radius = 50%
p
padding-top: 0
- .member,
- .assignee
+ .member
width: 50px
height: @width
margin-right: 10px