summaryrefslogtreecommitdiffstats
path: root/client/components/cards
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/components/cards
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/components/cards')
-rw-r--r--client/components/cards/cardDetails.jade40
-rw-r--r--client/components/cards/cardDetails.js104
-rw-r--r--client/components/cards/cardDetails.styl120
3 files changed, 262 insertions, 2 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