diff options
Diffstat (limited to 'client/components/cards')
-rw-r--r-- | client/components/cards/attachments.js | 2 | ||||
-rw-r--r-- | client/components/cards/cardDate.js | 16 | ||||
-rw-r--r-- | client/components/cards/cardDetails.jade | 69 | ||||
-rw-r--r-- | client/components/cards/cardDetails.js | 174 | ||||
-rw-r--r-- | client/components/cards/cardDetails.styl | 90 | ||||
-rw-r--r-- | client/components/cards/minicard.jade | 17 | ||||
-rw-r--r-- | client/components/cards/minicard.js | 35 | ||||
-rw-r--r-- | client/components/cards/minicard.styl | 16 |
8 files changed, 388 insertions, 31 deletions
diff --git a/client/components/cards/attachments.js b/client/components/cards/attachments.js index 843f1eb7..e4439155 100644 --- a/client/components/cards/attachments.js +++ b/client/components/cards/attachments.js @@ -131,6 +131,8 @@ Template.previewClipboardImagePopup.onRendered(() => { direct(results); }, }); + } else { + direct(results); } } }; diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index 91205f1c..cb54b033 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -105,7 +105,7 @@ Template.dateBadge.helpers({ // editCardReceivedDatePopup (class extends DatePicker { onCreated() { - super.onCreated(); + super.onCreated(moment().format('YYYY-MM-DD HH:mm')); this.data().getReceived() && this.date.set(moment(this.data().getReceived())); } @@ -122,7 +122,7 @@ Template.dateBadge.helpers({ // editCardStartDatePopup (class extends DatePicker { onCreated() { - super.onCreated(); + super.onCreated(moment().format('YYYY-MM-DD HH:mm')); this.data().getStart() && this.date.set(moment(this.data().getStart())); } @@ -148,7 +148,7 @@ Template.dateBadge.helpers({ // editCardDueDatePopup (class extends DatePicker { onCreated() { - super.onCreated(); + super.onCreated('1970-01-01 17:00:00'); this.data().getDue() && this.date.set(moment(this.data().getDue())); } @@ -171,7 +171,7 @@ Template.dateBadge.helpers({ // editCardEndDatePopup (class extends DatePicker { onCreated() { - super.onCreated(); + super.onCreated(moment().format('YYYY-MM-DD HH:mm')); this.data().getEnd() && this.date.set(moment(this.data().getEnd())); } @@ -237,7 +237,7 @@ class CardReceivedDate extends CardDate { const theDate = this.date.get(); // if dueAt, endAt and startAt exist & are > receivedAt, receivedAt doesn't need to be flagged if ( - (startAt && theDate.isAfter(dueAt)) || + (startAt && theDate.isAfter(startAt)) || (endAt && theDate.isAfter(endAt)) || (dueAt && theDate.isAfter(dueAt)) ) @@ -344,9 +344,9 @@ class CardEndDate extends CardDate { let classes = 'end-date' + ' '; const dueAt = this.data().getDue(); const theDate = this.date.get(); - if (theDate.diff(dueAt, 'days') >= 2) classes += 'long-overdue'; - else if (theDate.diff(dueAt, 'days') >= 0) classes += 'due'; - else if (theDate.diff(dueAt, 'days') >= -2) classes += 'almost-due'; + if (!dueAt) classes += ''; + else if (theDate.isBefore(dueAt)) classes += 'current'; + else if (theDate.isAfter(dueAt)) classes += 'due'; return classes; } diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index 13b6bd13..2b4f44b9 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -4,9 +4,14 @@ template(name="cardDetails") +inlinedForm(classNames="js-card-details-title") +editCardTitleForm else - a.fa.fa-times-thin.close-card-details.js-close-card-details - if currentUser.isBoardMember - a.fa.fa-navicon.card-details-menu.js-open-card-details-menu + unless isMiniScreen + a.fa.fa-times-thin.close-card-details.js-close-card-details + if currentUser.isBoardMember + a.fa.fa-navicon.card-details-menu.js-open-card-details-menu + if isMiniScreen + a.fa.fa-times-thin.close-card-details-mobile-web.js-close-card-details + if currentUser.isBoardMember + a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu h2.card-details-title.js-card-title( class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}") +viewer @@ -73,6 +78,16 @@ 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 + +userAvatarAssignee(userId=this cardId=../_id) + | {{! XXX Hack to hide syntaxic coloration /// }} + if canModifyCard + unless assigneeSelected + 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 +311,54 @@ template(name="cardMembersPopup") if isCardMember i.fa.fa-check +template(name="cardAssigneesPopup") + ul.pop-over-list.js-card-assignee-list + each board.activeMembers + li.item(class="{{#if isCardAssignee}}active{{/if}}") + a.name.js-select-assignee(href="#") + +userAvatar(userId=user._id) + span.full-name + = user.profile.fullname + | (<span class="username">{{ user.username }}</span>) + 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.assignee-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 cd8813f5..7bb54223 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -121,11 +121,6 @@ BlazeComponent.extendComponent({ // Send Webhook but not create Activities records --- const card = this.currentData(); const userId = Meteor.userId(); - //console.log(`userId: ${userId}`); - //console.log(`cardId: ${card._id}`); - //console.log(`boardId: ${card.boardId}`); - //console.log(`listId: ${card.listId}`); - //console.log(`swimlaneId: ${card.swimlaneId}`); const params = { userId, cardId: card._id, @@ -134,16 +129,25 @@ BlazeComponent.extendComponent({ user: Meteor.user().username, url: '', }; - //console.log('looking for integrations...'); + const integrations = Integrations.find({ - boardId: card.boardId, - type: 'outgoing-webhooks', + boardId: { $in: [card.boardId, Integrations.Const.GLOBAL_WEBHOOK_ID] }, enabled: true, activities: { $in: ['CardDetailsRendered', 'all'] }, }).fetch(); - //console.log(`Investigation length: ${integrations.length}`); + if (integrations.length > 0) { - Meteor.call('outgoingWebhooks', integrations, 'CardSelected', params); + integrations.forEach(integration => { + Meteor.call( + 'outgoingWebhooks', + integration, + 'CardSelected', + params, + () => { + return; + }, + ); + }); } //------------- } @@ -309,6 +313,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'), @@ -321,6 +327,19 @@ BlazeComponent.extendComponent({ parentComponent.showOverlay.set(true); parentComponent.mouseHasEnterCardDetails = true; }, + 'mousedown .js-card-details'() { + Session.set('cardDetailsIsDragging', false); + Session.set('cardDetailsIsMouseDown', true); + }, + 'mousemove .js-card-details'() { + if (Session.get('cardDetailsIsMouseDown')) { + Session.set('cardDetailsIsDragging', true); + } + }, + 'mouseup .js-card-details'() { + Session.set('cardDetailsIsDragging', false); + Session.set('cardDetailsIsMouseDown', false); + }, 'click #toggleButton'() { Meteor.call('toggleSystemMessages'); }, @@ -329,6 +348,58 @@ 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, + }, + }); + }, + + assigneeSelected() { + if (this.getAssignees().length === 0) { + return false; + } else { + return true; + } + }, + + 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 { @@ -386,6 +457,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'), @@ -777,7 +849,14 @@ BlazeComponent.extendComponent({ EscapeActions.register( 'detailsPane', () => { - Utils.goBoardId(Session.get('currentBoard')); + if (Session.get('cardDetailsIsDragging')) { + // Reset dragging status as the mouse landed outside the cardDetails template area and this will prevent a mousedown event from firing + Session.set('cardDetailsIsDragging', false); + Session.set('cardDetailsIsMouseDown', false); + } else { + // Prevent close card when the user is selecting text and moves the mouse cursor outside the card detail area + Utils.goBoardId(Session.get('currentBoard')); + } }, () => { return !Session.equals('currentCard', null); @@ -786,3 +865,76 @@ 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.cardAssigneesPopup.helpers({ + isCardAssignee() { + const card = Template.parentData(); + const cardAssignees = card.getAssignees(); + + return _.contains(cardAssignees, this.userId); + }, + + user() { + return Users.findOne(this.userId); + }, +}); + +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 cd475072..3fc4d047 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -1,5 +1,80 @@ @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 + + + + &.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 + +// Other card details + .card-details padding: 0 flex-shrink: 0 @@ -32,7 +107,9 @@ border-bottom: 1px solid darken(white, 14%) .close-card-details, - .card-details-menu + .card-details-menu, + .close-card-details-mobile-web, + .card-details-menu-mobile-web float: right .close-card-details @@ -40,10 +117,20 @@ padding: 5px margin-right: -8px + .close-card-details-mobile-web + font-size: 24px + padding: 5px + margin-right: 40px + .card-details-menu font-size: 17px padding: 10px + .card-details-menu-mobile-web + font-size: 17px + padding: 10px + margin-right: 30px + .card-details-watch font-size: 17px padding-left: 7px @@ -93,6 +180,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, diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade index 3806ce41..79672f8c 100644 --- a/client/components/cards/minicard.jade +++ b/client/components/cards/minicard.jade @@ -3,6 +3,13 @@ template(name="minicard") class="{{#if isLinkedCard}}linked-card{{/if}}" class="{{#if isLinkedBoard}}linked-board{{/if}}" class="minicard-{{colorClass}}") + if isMiniScreen + .handle + .fa.fa-arrows + unless isMiniScreen + if showDesktopDragHandles + .handle + .fa.fa-arrows if cover .minicard-cover(style="background-image: url('{{cover.url}}');") if labels @@ -15,8 +22,6 @@ template(name="minicard") if hiddenMinicardLabelText .minicard-label(class="card-label-{{color}}" title="{{name}}") .minicard-title - .handle - .fa.fa-arrows if $eq 'prefix-with-full-path' currentBoard.presentParentTask .parent-prefix | {{ parentString ' > ' }} @@ -53,6 +58,8 @@ template(name="minicard") if getDue .date +minicardDueDate + if getEnd + +minicardEndDate if getSpentTime .date +cardSpentTime @@ -69,6 +76,12 @@ template(name="minicard") +viewer = trueValue + if getAssignees + .minicard-assignees.js-minicard-assignees + each getAssignees + +userAvatar(userId=this) + hr + if getMembers .minicard-members.js-minicard-members each getMembers diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js index 4c25c11d..a9f92dec 100644 --- a/client/components/cards/minicard.js +++ b/client/components/cards/minicard.js @@ -18,7 +18,13 @@ BlazeComponent.extendComponent({ }, { 'click .js-toggle-minicard-label-text'() { - Meteor.call('toggleMinicardLabelText'); + import { Cookies } from 'meteor/ostrio:cookies'; + const cookies = new Cookies(); + if (cookies.has('hiddenMinicardLabelText')) { + cookies.remove('hiddenMinicardLabelText'); //true + } else { + cookies.set('hiddenMinicardLabelText', 'true'); //true + } }, }, ]; @@ -26,7 +32,32 @@ BlazeComponent.extendComponent({ }).register('minicard'); Template.minicard.helpers({ + showDesktopDragHandles() { + currentUser = Meteor.user(); + if (currentUser) { + return (currentUser.profile || {}).showDesktopDragHandles; + } else { + import { Cookies } from 'meteor/ostrio:cookies'; + const cookies = new Cookies(); + if (cookies.has('showDesktopDragHandles')) { + return true; + } else { + return false; + } + } + }, hiddenMinicardLabelText() { - return Meteor.user().hasHiddenMinicardLabelText(); + currentUser = Meteor.user(); + if (currentUser) { + return (currentUser.profile || {}).hiddenMinicardLabelText; + } else { + import { Cookies } from 'meteor/ostrio:cookies'; + const cookies = new Cookies(); + if (cookies.has('hiddenMinicardLabelText')) { + return true; + } else { + return false; + } + } }, }); diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl index c4172572..8607e118 100644 --- a/client/components/cards/minicard.styl +++ b/client/components/cards/minicard.styl @@ -105,7 +105,7 @@ right: 5px; top: 5px; display:none; - @media only screen and (max-width: 1199px) { + @media only screen { display:block; } .fa-arrows @@ -160,9 +160,10 @@ padding-left: 0px line-height: 12px - .minicard-members + .minicard-members, + .minicard-assignees float: right - margin: 2px -8px -2px 0 + margin: 2px -8px 12px 0 .member float: right @@ -170,10 +171,17 @@ height: 28px width: @height + .assignee + float: right + border-radius: 50% + height: 28px + width: @height + + .badges margin-top: 10px - .minicard-members:empty + .minicard-members:empty, + .minicard-assignees:empty display: none &.minicard-composer |