summaryrefslogtreecommitdiffstats
path: root/client/components/cards
diff options
context:
space:
mode:
author蔡仲明 (Romulus Urakagi Tsai) <urakagi@gmail.com>2019-11-21 11:25:56 +0800
committerGitHub <noreply@github.com>2019-11-21 11:25:56 +0800
commit3e0bedd8c7a6dec97352212adb1cbde1ade44190 (patch)
tree651ff30d25ddb0416444370368d699e597c142d7 /client/components/cards
parent9bbeb73db1cd0ce1caaaca8dfb14ea92131bbf9d (diff)
parent4f5de87cc4c2281bd576548693de7c94e6a988c6 (diff)
downloadwekan-3e0bedd8c7a6dec97352212adb1cbde1ade44190.tar.gz
wekan-3e0bedd8c7a6dec97352212adb1cbde1ade44190.tar.bz2
wekan-3e0bedd8c7a6dec97352212adb1cbde1ade44190.zip
Merge pull request #1 from wekan/master
Update master
Diffstat (limited to 'client/components/cards')
-rw-r--r--client/components/cards/attachments.js2
-rw-r--r--client/components/cards/cardDate.js16
-rw-r--r--client/components/cards/cardDetails.jade69
-rw-r--r--client/components/cards/cardDetails.js174
-rw-r--r--client/components/cards/cardDetails.styl90
-rw-r--r--client/components/cards/minicard.jade17
-rw-r--r--client/components/cards/minicard.js35
-rw-r--r--client/components/cards/minicard.styl16
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