summaryrefslogtreecommitdiffstats
path: root/client/components/cards
diff options
context:
space:
mode:
Diffstat (limited to 'client/components/cards')
-rw-r--r--client/components/cards/attachments.jade28
-rw-r--r--client/components/cards/cardDate.js3
-rw-r--r--client/components/cards/cardDetails.jade525
-rw-r--r--client/components/cards/cardDetails.js193
-rw-r--r--client/components/cards/cardDetails.styl35
-rw-r--r--client/components/cards/checklists.jade7
-rw-r--r--client/components/cards/checklists.js37
-rw-r--r--client/components/cards/checklists.styl5
-rw-r--r--client/components/cards/labels.styl2
-rw-r--r--client/components/cards/minicard.jade4
-rw-r--r--client/components/cards/minicard.js16
-rw-r--r--client/components/cards/minicard.styl2
-rw-r--r--client/components/cards/subtasks.jade4
-rw-r--r--client/components/cards/subtasks.js26
14 files changed, 657 insertions, 230 deletions
diff --git a/client/components/cards/attachments.jade b/client/components/cards/attachments.jade
index 2a96f4f4..61454fa7 100644
--- a/client/components/cards/attachments.jade
+++ b/client/components/cards/attachments.jade
@@ -38,18 +38,22 @@ template(name="attachmentsGalery")
| {{_ 'download'}}
if currentUser.isBoardMember
unless currentUser.isCommentOnly
- if isImage
- a(class="{{#if $eq ../coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}")
- i.fa.fa-thumb-tack
- if($eq ../coverId _id)
- | {{_ 'remove-cover'}}
- else
- | {{_ 'add-cover'}}
- a.js-confirm-delete
- i.fa.fa-close
- | {{_ 'delete'}}
+ unless currentUser.isWorker
+ if isImage
+ a(class="{{#if $eq ../coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}")
+ i.fa.fa-thumb-tack
+ if($eq ../coverId _id)
+ | {{_ 'remove-cover'}}
+ else
+ | {{_ 'add-cover'}}
+ a.js-confirm-delete
+ i.fa.fa-close
+ | {{_ 'delete'}}
if currentUser.isBoardMember
unless currentUser.isCommentOnly
- li.attachment-item.add-attachment
- a.js-add-attachment {{_ 'add-attachment' }}
+ unless currentUser.isWorker
+ //li.attachment-item.add-attachment
+ a.js-add-attachment
+ i.fa.fa-plus
+ | {{_ 'add-attachment' }}
diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js
index cb54b033..c4b5c6d8 100644
--- a/client/components/cards/cardDate.js
+++ b/client/components/cards/cardDate.js
@@ -97,7 +97,8 @@ Template.dateBadge.helpers({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
});
diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade
index 2b4f44b9..ae97e0e9 100644
--- a/client/components/cards/cardDetails.jade
+++ b/client/components/cards/cardDetails.jade
@@ -8,16 +8,23 @@ template(name="cardDetails")
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
+ input.inline-input(type="text" id="cardURL_copy" value="{{ absoluteUrl }}")
+ a.fa.fa-link.card-copy-button.js-copy-link(
+ class="fa-link"
+ title="{{_ 'copy-card-link-to-clipboard'}}"
+ value="{{ absoluteUrl }}"
+ )
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
+ a.fa.fa-link.card-copy-mobile-button
h2.card-details-title.js-card-title(
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
+viewer
= getTitle
- if isWatching
- i.fa.fa-eye.card-details-watch
+ if isWatching
+ i.card-details-watch.fa.fa-eye
.card-details-path
each parentList
|   >  
@@ -25,7 +32,7 @@ template(name="cardDetails")
// else
{{_ 'top-level-card'}}
if isLinkedCard
- h3.linked-card-location
+ a.linked-card-location.js-go-to-linked-card
+viewer
| {{getBoardTitle}} > {{getTitle}}
@@ -36,70 +43,105 @@ template(name="cardDetails")
p.warning {{_ 'card-archived'}}
.card-details-items
- .card-details-item.card-details-item-received
- h3.card-details-item-title {{_ 'card-received'}}
- if getReceived
- +cardReceivedDate
- else
- if canModifyCard
- a.js-received-date {{_ 'add'}}
-
- .card-details-item.card-details-item-start
- h3.card-details-item-title {{_ 'card-start'}}
- if getStart
- +cardStartDate
- else
- if canModifyCard
- a.js-start-date {{_ 'add'}}
-
- .card-details-item.card-details-item-due
- h3.card-details-item-title {{_ 'card-due'}}
- if getDue
- +cardDueDate
- else
+ if currentBoard.allowsReceivedDate
+ .card-details-item.card-details-item-received
+ h3
+ i.fa.fa-sign-out
+ card-details-item-title {{_ 'card-received'}}
+ if getReceived
+ +cardReceivedDate
+ else
+ if canModifyCard
+ unless currentUser.isWorker
+ a.card-label.add-label.js-received-date
+ i.fa.fa-plus
+
+ if currentBoard.allowsStartDate
+ .card-details-item.card-details-item-start
+ h3
+ i.fa.fa-hourglass-start
+ card-details-item-title {{_ 'card-start'}}
+ if getStart
+ +cardStartDate
+ else
+ if canModifyCard
+ unless currentUser.isWorker
+ a.card-label.add-label.js-start-date
+ i.fa.fa-plus
+
+ if currentBoard.allowsDueDate
+ .card-details-item.card-details-item-due
+ h3
+ i.fa.fa-sign-in
+ card-details-item-title {{_ 'card-due'}}
+ if getDue
+ +cardDueDate
+ else
+ if canModifyCard
+ unless currentUser.isWorker
+ a.card-label.add-label.js-due-date
+ i.fa.fa-plus
+
+ if currentBoard.allowsEndDate
+ .card-details-item.card-details-item-end
+ h3
+ i.fa.fa-hourglass-end
+ card-details-item-title {{_ 'card-end'}}
+ if getEnd
+ +cardEndDate
+ else
+ if canModifyCard
+ unless currentUser.isWorker
+ a.card-label.add-label.js-end-date
+ i.fa.fa-plus
+
+ //.card-details-items
+ if currentBoard.allowsMembers
+ .card-details-item.card-details-item-members
+ h3
+ i.fa.fa-users
+ card-details-item-title {{_ 'members'}}
+ each getMembers
+ +userAvatar(userId=this cardId=../_id)
+ | {{! XXX Hack to hide syntaxic coloration /// }}
if canModifyCard
- a.js-due-date {{_ 'add'}}
-
- .card-details-item.card-details-item-end
- h3.card-details-item-title {{_ 'card-end'}}
- if getEnd
- +cardEndDate
- else
+ unless currentUser.isWorker
+ a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
+ i.fa.fa-plus
+
+ //if assigneeSelected
+ if currentBoard.allowsAssignee
+ .card-details-item.card-details-item-assignees
+ h3
+ i.fa.fa-user
+ card-details-item-title {{_ 'assignee'}}
+ each getAssignees
+ +userAvatarAssignee(userId=this cardId=../_id)
+ | {{! XXX Hack to hide syntaxic coloration /// }}
if canModifyCard
- a.js-end-date {{_ 'add'}}
-
- .card-details-items
- .card-details-item.card-details-item-members
- h3.card-details-item-title {{_ 'members'}}
- each getMembers
- +userAvatar(userId=this cardId=../_id)
- | {{! XXX Hack to hide syntaxic coloration /// }}
- if canModifyCard
- 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
+ if currentUser.isWorker
+ unless assigneeSelected
+ a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
+ i.fa.fa-plus
+
+ if currentBoard.allowsLabels
+ .card-details-item.card-details-item-labels
+ h3
+ i.fa.fa-tags
+ card-details-item-title {{_ 'labels'}}
+ a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
+ each labels
+ span.card-label(class="card-label-{{color}}" title=name)
+ +viewer
+ = name
+ if canModifyCard
+ unless currentUser.isWorker
+ a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
+ 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'}}")
- each labels
- span.card-label(class="card-label-{{color}}" title=name)
- +viewer
- = name
- if canModifyCard
- a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
- i.fa.fa-plus
-
- .card-details-items
+ //.card-details-items
each customFieldsWD
.card-details-item.card-details-item-customfield
h3.card-details-item-title
@@ -107,7 +149,7 @@ template(name="cardDetails")
= definition.name
+cardCustomField
- .card-details-items
+ //.card-details-items
if getSpentTime
.card-details-item.card-details-item-spent
if getIsOvertime
@@ -116,84 +158,124 @@ template(name="cardDetails")
h3.card-details-item-title {{_ 'spent-time-hours'}}
+cardSpentTime
- //- XXX We should use "editable" to avoid repetiting ourselves
- if canModifyCard
- h3.card-details-item-title {{_ 'description'}}
- +inlinedCardDescription(classNames="card-description js-card-description")
- +editor(autofocus=true)
- | {{getUnsavedValue 'cardDescription' _id getDescription}}
- .edit-controls.clearfix
- button.primary(type="submit") {{_ 'save'}}
- a.fa.fa-times-thin.js-close-inlined-form
- else
- a.js-open-inlined-form
- if getDescription
+ //.card-details-items
+ if currentBoard.allowsRequestedBy
+ .card-details-item.card-details-item-name
+ h3
+ i.fa.fa-shopping-cart
+ card-details-item-title {{_ 'requested-by'}}
+ if canModifyCard
+ unless currentUser.isWorker
+ +inlinedForm(classNames="js-card-details-requester")
+ +editCardRequesterForm
+ else
+ a.js-open-inlined-form
+ if getRequestedBy
+ +viewer
+ = getRequestedBy
+ else
+ | {{_ 'add'}}
+ else if getRequestedBy
+viewer
- = getDescription
- else
- | {{_ 'edit'}}
- if (hasUnsavedValue 'cardDescription' _id)
- p.quiet
- | {{_ 'unsaved-description'}}
- a.js-open-inlined-form {{_ 'view-it'}}
- = ' - '
- a.js-close-inlined-form {{_ 'discard'}}
- else if getDescription
- h3.card-details-item-title {{_ 'description'}}
- +viewer
- = getDescription
+ = getRequestedBy
- .card-details-items
- .card-details-item.card-details-item-name
- h3.card-details-item-title {{_ 'requested-by'}}
- if canModifyCard
- +inlinedForm(classNames="js-card-details-requester")
- +editCardRequesterForm
- else
- a.js-open-inlined-form
- if getRequestedBy
- +viewer
- = getRequestedBy
- else
- | {{_ 'add'}}
- else if getRequestedBy
- +viewer
- = getRequestedBy
-
- .card-details-item.card-details-item-name
- h3.card-details-item-title {{_ 'assigned-by'}}
- if canModifyCard
- +inlinedForm(classNames="js-card-details-assigner")
- +editCardAssignerForm
- else
- a.js-open-inlined-form
- if getAssignedBy
- +viewer
- = getAssignedBy
+ if currentBoard.allowsAssignedBy
+ .card-details-item.card-details-item-name
+ h3
+ i.fa.fa-user-plus
+ card-details-item-title {{_ 'assigned-by'}}
+ if canModifyCard
+ unless currentUser.isWorker
+ +inlinedForm(classNames="js-card-details-assigner")
+ +editCardAssignerForm
else
- | {{_ 'add'}}
- else if getRequestedBy
- +viewer
- = getAssignedBy
-
- hr
- +checklists(cardId = _id)
+ a.js-open-inlined-form
+ if getAssignedBy
+ +viewer
+ = getAssignedBy
+ else
+ | {{_ 'add'}}
+ else if getRequestedBy
+ +viewer
+ = getAssignedBy
- if currentBoard.allowsSubtasks
+ if getVoteQuestion
hr
- +subtasks(cardId = _id)
-
- hr
- h3
- i.fa.fa-paperclip
- | {{_ 'attachments'}}
+ .vote-title
+ h3
+ i.fa.fa-thumbs-up
+ card-details-item-title {{_ 'vote-question'}}
+ .vote-result
+ if votePublic
+ a.card-label.card-label-green.js-show-positive-votes {{ voteCountPositive }}
+ a.card-label.card-label-red.js-show-negative-votes {{ voteCountNegative }}
+ else
+ .card-label.card-label-green {{ voteCountPositive }}
+ .card-label.card-label-red {{ voteCountNegative }}
+ +viewer
+ = getVoteQuestion
+ button.card-details-green.js-vote.js-vote-positive(class="{{#if voteState}}voted{{/if}}") {{_ 'vote-for-it'}}
+ button.card-details-red.js-vote.js-vote-negative(class="{{#if $eq voteState false}}voted{{/if}}") {{_ 'vote-against'}}
- +attachmentsGalery
+ //- XXX We should use "editable" to avoid repetiting ourselves
+ if canModifyCard
+ unless currentUser.isWorker
+ if currentBoard.allowsDescriptionTitle
+ hr
+ h3
+ i.fa.fa-align-left
+ card-details-item-title {{_ 'description'}}
+ if currentBoard.allowsDescriptionText
+ +inlinedCardDescription(classNames="card-description js-card-description")
+ +editor(autofocus=true)
+ | {{getUnsavedValue 'cardDescription' _id getDescription}}
+ .edit-controls.clearfix
+ button.primary(type="submit") {{_ 'save'}}
+ a.fa.fa-times-thin.js-close-inlined-form
+ else
+ if currentBoard.allowsDescriptionText
+ a.js-open-inlined-form
+ if getDescription
+ +viewer
+ = getDescription
+ else
+ | {{_ 'edit'}}
+ if (hasUnsavedValue 'cardDescription' _id)
+ p.quiet
+ | {{_ 'unsaved-description'}}
+ a.js-open-inlined-form {{_ 'view-it'}}
+ = ' - '
+ a.js-close-inlined-form {{_ 'discard'}}
+ else if getDescription
+ if currentBoard.allowsDescriptionTitle
+ hr
+ h3.card-details-item-title {{_ 'description'}}
+ if currentBoard.allowsDescriptionText
+ +viewer
+ = getDescription
+
+ .card-checklist-attachmentGalerys
+ .card-checklist-attachmentGalery.card-checklists
+ if currentBoard.allowsChecklists
+ hr
+ +checklists(cardId = _id)
+ if currentBoard.allowsSubtasks
+ hr
+ +subtasks(cardId = _id)
+ if currentBoard.allowsAttachments
+ hr
+ h3
+ i.fa.fa-paperclip
+ | {{_ 'attachments'}}
+ .card-checklist-attachmentGalery.card-attachmentGalery
+ +attachmentsGalery
hr
unless currentUser.isNoComments
.activity-title
- h3 {{ _ 'activity'}}
+ h3
+ i.fa.fa-history
+ | {{ _ 'activity'}}
if currentUser.isBoardMember
.material-toggle-switch
span.toggle-switch-title {{_ 'hide-system-messages'}}
@@ -202,9 +284,10 @@ template(name="cardDetails")
else
input.toggle-switch(type="checkbox" id="toggleButton")
label.toggle-label(for="toggleButton")
- if currentUser.isBoardMember
- unless currentUser.isNoComments
- +commentForm
+ if currentBoard.allowsComments
+ if currentUser.isBoardMember
+ unless currentUser.isNoComments
+ +commentForm
unless currentUser.isNoComments
if isLoaded.get
if isLinkedCard
@@ -235,32 +318,89 @@ template(name="editCardAssignerForm")
template(name="cardDetailsActionsPopup")
ul.pop-over-list
- li: a.js-toggle-watch-card {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
+ li
+ a.js-toggle-watch-card
+ if isWatching
+ i.fa.fa-eye
+ | {{_ 'unwatch'}}
+ else
+ i.fa.fa-eye-slash
+ | {{_ 'watch'}}
if canModifyCard
- hr
- ul.pop-over-list
- //li: a.js-members {{_ 'card-edit-members'}}
- //li: a.js-labels {{_ 'card-edit-labels'}}
- //li: a.js-attachments {{_ 'card-edit-attachments'}}
- li: a.js-custom-fields {{_ 'card-edit-custom-fields'}}
- //li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
- //li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
- //li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
- //li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
- li: a.js-spent-time {{_ 'editCardSpentTimePopup-title'}}
- li: a.js-set-card-color {{_ 'setCardColorPopup-title'}}
- hr
- ul.pop-over-list
- li: a.js-move-card-to-top {{_ 'moveCardToTop-title'}}
- li: a.js-move-card-to-bottom {{_ 'moveCardToBottom-title'}}
- hr
+ unless currentUser.isWorker
+ hr
+ ul.pop-over-list
+ //li: a.js-members {{_ 'card-edit-members'}}
+ //li: a.js-labels {{_ 'card-edit-labels'}}
+ //li: a.js-attachments {{_ 'card-edit-attachments'}}
+ if getVoteQuestion
+ li
+ a.js-cancel-voting
+ i.fa.fa-thumbs-up
+ | {{_ 'card-cancel-voting'}}
+ else
+ li
+ a.js-start-voting
+ i.fa.fa-thumbs-up
+ | {{_ 'card-start-voting'}}
+ li
+ a.js-custom-fields
+ i.fa.fa-list-alt
+ | {{_ 'card-edit-custom-fields'}}
+ //li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
+ //li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
+ //li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
+ //li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
+ li
+ a.js-spent-time
+ i.fa.fa-clock-o
+ | {{_ 'editCardSpentTimePopup-title'}}
+ li
+ a.js-set-card-color
+ i.fa.fa-paint-brush
+ | {{_ 'setCardColorPopup-title'}}
+ hr
ul.pop-over-list
- li: a.js-move-card {{_ 'moveCardPopup-title'}}
- li: a.js-copy-card {{_ 'copyCardPopup-title'}}
- li: a.js-copy-checklist-cards {{_ 'copyChecklistToManyCardsPopup-title'}}
+ li
+ a.js-move-card-to-top
+ i.fa.fa-arrow-up
+ | {{_ 'moveCardToTop-title'}}
+ li
+ a.js-move-card-to-bottom
+ i.fa.fa-arrow-down
+ | {{_ 'moveCardToBottom-title'}}
+ unless currentUser.isWorker
+ hr
+ ul.pop-over-list
+ li
+ a.js-move-card
+ i.fa.fa-arrow-right
+ | {{_ 'moveCardPopup-title'}}
+ li
+ a.js-copy-card
+ i.fa.fa-copy
+ | {{_ 'copyCardPopup-title'}}
+ hr
+ ul.pop-over-list
+ li
+ a.js-copy-checklist-cards
+ i.fa.fa-list
+ i.fa.fa-copy
+ | {{_ 'copyChecklistToManyCardsPopup-title'}}
unless archived
- li: a.js-archive {{_ 'archive-card'}}
- li: a.js-more {{_ 'cardMorePopup-title'}}
+ hr
+ ul.pop-over-list
+ li
+ a.js-archive
+ i.fa.fa-arrow-right
+ i.fa.fa-archive
+ | {{_ 'archive-card'}}
+ hr
+ ul.pop-over-list
+ li
+ a.js-more
+ i.fa.fa-link
+ | {{_ 'cardMorePopup-title'}}
template(name="moveCardPopup")
+boardsAndLists
@@ -312,16 +452,27 @@ template(name="cardMembersPopup")
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
+ unless currentUser.isWorker
+ 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
+ if currentUser.isWorker
+ ul.pop-over-list.js-card-assignee-list
+ li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}")
+ a.name.js-select-assignee(href="#")
+ +userAvatar(userId=currentUser._id)
+ span.full-name
+ = currentUser.profile.fullname
+ | (<span class="username">{{ currentUser.username }}</span>)
+ if currentUser.isCardAssignee
+ i.fa.fa-check
template(name="userAvatarAssignee")
a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})")
@@ -349,11 +500,13 @@ template(name="cardAssigneePopup")
p.quiet @{{ user.username }}
ul.pop-over-list
if currentUser.isNotCommentOnly
+ unless currentUser.isWorker
li: a.js-remove-assignee {{_ 'remove-member-from-card'}}
- if $eq currentUser._id user._id
- with currentUser
- li: a.js-edit-profile {{_ 'edit-profile'}}
+ unless currentUser.isWorker
+ 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")
@@ -413,3 +566,35 @@ template(name="cardDeletePopup")
unless archived
p {{_ "card-delete-suggest-archive"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
+
+template(name="cardStartVotingPopup")
+ form.edit-vote-question
+ .fields
+ label(for="vote") {{_ 'vote-question'}}
+ input.js-vote-field#vote(type="text" name="vote" value="{{card.getVoteQuestion}}" autofocus)
+ label(for="vote-public") {{_ 'vote-public'}}
+ a.js-toggle-vote-public
+ .materialCheckBox#vote-public(name="vote-public")
+
+ button.primary.confirm.js-submit {{_ 'save'}}
+ //- button.js-remove-color.negate.wide.right {{_ 'delete'}}
+
+template(name="positiveVoteMembersPopup")
+ ul.pop-over-list.js-card-member-list
+ each m in voteMemberPositive
+ li.item
+ a.name
+ +userAvatar(userId=m._id)
+ span.full-name
+ = m.profile.fullname
+ | (<span class="username">{{ m.username }}</span>)
+
+template(name="negativeVoteMembersPopup")
+ ul.pop-over-list.js-card-member-list
+ each m in voteMemberNegative
+ li.item
+ a.name
+ +userAvatar(userId=m._id)
+ span.full-name
+ = m.profile.fullname
+ | (<span class="username">{{ m.username }}</span>)
diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js
index 67120043..271fbe2f 100644
--- a/client/components/cards/cardDetails.js
+++ b/client/components/cards/cardDetails.js
@@ -1,5 +1,5 @@
const subManager = new SubsManager();
-const { calculateIndexData, enableClickOnTouch } = Utils;
+const { calculateIndexData } = Utils;
let cardColors;
Meteor.startup(() => {
@@ -38,6 +38,37 @@ BlazeComponent.extendComponent({
Meteor.subscribe('unsaved-edits');
},
+ voteState() {
+ const card = this.currentData();
+ const userId = Meteor.userId();
+ let state;
+ if (card.vote) {
+ if (card.vote.positive) {
+ state = _.contains(card.vote.positive, userId);
+ if (state === true) return true;
+ }
+ if (card.vote.negative) {
+ state = _.contains(card.vote.negative, userId);
+ if (state === true) return false;
+ }
+ }
+ return null;
+ },
+ votePublic() {
+ const card = this.currentData();
+ if (card.vote) return card.vote.public;
+ return null;
+ },
+ voteCountPositive() {
+ const card = this.currentData();
+ if (card.vote && card.vote.positive) return card.vote.positive.length;
+ return null;
+ },
+ voteCountNegative() {
+ const card = this.currentData();
+ if (card.vote && card.vote.negative) return card.vote.negative.length;
+ return null;
+ },
isWatching() {
const card = this.currentData();
return card.findWatcher(Meteor.userId());
@@ -51,7 +82,8 @@ BlazeComponent.extendComponent({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
@@ -199,9 +231,6 @@ BlazeComponent.extendComponent({
},
});
- // ugly touch event hotfix
- enableClickOnTouch('.card-checklist-items .js-checklist');
-
const $subtasksDom = this.$('.card-subtasks-items');
$subtasksDom.sortable({
@@ -237,20 +266,21 @@ BlazeComponent.extendComponent({
},
});
- // ugly touch event hotfix
- enableClickOnTouch('.card-subtasks-items .js-subtasks');
-
function userIsMember() {
return Meteor.user() && Meteor.user().isBoardMember();
}
// Disable sorting if the current user is not a board member
this.autorun(() => {
- if ($checklistsDom.data('sortable')) {
- $checklistsDom.sortable('option', 'disabled', !userIsMember());
+ const disabled = !userIsMember() || Utils.isMiniScreen();
+ if (
+ $checklistsDom.data('uiSortable') ||
+ $checklistsDom.data('sortable')
+ ) {
+ $checklistsDom.sortable('option', 'disabled', disabled);
}
- if ($subtasksDom.data('sortable')) {
- $subtasksDom.sortable('option', 'disabled', !userIsMember());
+ if ($subtasksDom.data('uiSortable') || $subtasksDom.data('sortable')) {
+ $subtasksDom.sortable('option', 'disabled', disabled);
}
});
},
@@ -278,6 +308,29 @@ BlazeComponent.extendComponent({
'click .js-close-card-details'() {
Utils.goBoardId(this.data().boardId);
},
+ 'click .js-copy-link'() {
+ StringToCopyElement = document.getElementById('cardURL_copy');
+ StringToCopyElement.select();
+ if (document.execCommand('copy')) {
+ StringToCopyElement.blur();
+ } else {
+ document.getElementById('cardURL_copy').selectionStart = 0;
+ document.getElementById('cardURL_copy').selectionEnd = 999;
+ document.execCommand('copy');
+ if (window.getSelection) {
+ if (window.getSelection().empty) {
+ // Chrome
+ window.getSelection().empty();
+ } else if (window.getSelection().removeAllRanges) {
+ // Firefox
+ window.getSelection().removeAllRanges();
+ }
+ } else if (document.selection) {
+ // IE?
+ document.selection.empty();
+ }
+ }
+ },
'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
'submit .js-card-description'(event) {
event.preventDefault();
@@ -317,6 +370,9 @@ BlazeComponent.extendComponent({
this.data().setRequestedBy('');
}
},
+ 'click .js-go-to-linked-card'() {
+ Utils.goCardId(this.data().linkedId);
+ },
'click .js-member': Popup.open('cardMember'),
'click .js-add-members': Popup.open('cardMembers'),
'click .js-assignee': Popup.open('cardAssignee'),
@@ -326,6 +382,8 @@ BlazeComponent.extendComponent({
'click .js-start-date': Popup.open('editCardStartDate'),
'click .js-due-date': Popup.open('editCardDueDate'),
'click .js-end-date': Popup.open('editCardEndDate'),
+ 'click .js-show-positive-votes': Popup.open('positiveVoteMembers'),
+ 'click .js-show-negative-votes': Popup.open('negativeVoteMembers'),
'mouseenter .js-card-details'() {
const parentComponent = this.parentComponent().parentComponent();
//on mobile view parent is Board, not BoardBody.
@@ -349,6 +407,18 @@ BlazeComponent.extendComponent({
'click #toggleButton'() {
Meteor.call('toggleSystemMessages');
},
+ 'click .js-vote'(e) {
+ const forIt = $(e.target).hasClass('js-vote-positive');
+ let newState = null;
+ if (
+ this.voteState() === null ||
+ (this.voteState() === false && forIt) ||
+ (this.voteState() === true && !forIt)
+ ) {
+ newState = forIt;
+ }
+ this.data().setVote(Meteor.userId(), newState);
+ },
},
];
},
@@ -370,6 +440,54 @@ Template.cardDetails.helpers({
});
},
+ receivedSelected() {
+ if (this.getReceived().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ startSelected() {
+ if (this.getStart().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ endSelected() {
+ if (this.getEnd().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ dueSelected() {
+ if (this.getDue().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ memberSelected() {
+ if (this.getMembers().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ labelSelected() {
+ if (this.getLabels().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
assigneeSelected() {
if (this.getAssignees().length === 0) {
return false;
@@ -378,6 +496,22 @@ Template.cardDetails.helpers({
}
},
+ requestBySelected() {
+ if (this.getRequestBy().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ assigneeBySelected() {
+ if (this.getAssigneeBy().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
memberType() {
const user = Users.findOne(this.userId);
return user && user.isBoardAdmin() ? 'admin' : 'normal';
@@ -466,6 +600,7 @@ Template.cardDetailsActionsPopup.events({
'click .js-assignees': Popup.open('cardAssignees'),
'click .js-labels': Popup.open('cardLabels'),
'click .js-attachments': Popup.open('cardAttachments'),
+ 'click .js-start-voting': Popup.open('cardStartVoting'),
'click .js-custom-fields': Popup.open('cardCustomFields'),
'click .js-received-date': Popup.open('editCardReceivedDate'),
'click .js-start-date': Popup.open('editCardStartDate'),
@@ -476,6 +611,11 @@ Template.cardDetailsActionsPopup.events({
'click .js-copy-card': Popup.open('copyCard'),
'click .js-copy-checklist-cards': Popup.open('copyChecklistToManyCards'),
'click .js-set-card-color': Popup.open('setCardColor'),
+ 'click .js-cancel-voting'(event) {
+ event.preventDefault();
+ this.unsetVote();
+ Popup.close();
+ },
'click .js-move-card-to-top'(event) {
event.preventDefault();
const minOrder = _.min(
@@ -578,7 +718,7 @@ BlazeComponent.extendComponent({
_id: { $ne: Meteor.user().getTemplatesBoardId() },
},
{
- sort: ['title'],
+ sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -754,7 +894,7 @@ BlazeComponent.extendComponent({
},
},
{
- sort: ['title'],
+ sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -851,6 +991,31 @@ BlazeComponent.extendComponent({
},
}).register('cardMorePopup');
+BlazeComponent.extendComponent({
+ onCreated() {
+ this.currentCard = this.currentData();
+ this.voteQuestion = new ReactiveVar(this.currentCard.voteQuestion);
+ },
+
+ events() {
+ return [
+ {
+ 'submit .edit-vote-question'(evt) {
+ evt.preventDefault();
+ const voteQuestion = evt.target.vote.value;
+ const publicVote = $('#vote-public').hasClass('is-checked');
+ this.currentCard.setVoteQuestion(voteQuestion, publicVote);
+ Popup.close();
+ },
+ 'click a.js-toggle-vote-public'(event) {
+ event.preventDefault();
+ $('#vote-public').toggleClass('is-checked');
+ },
+ },
+ ];
+ },
+}).register('cardStartVotingPopup');
+
// Close the card details pane by pressing escape
EscapeActions.register(
'detailsPane',
diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl
index 3fc4d047..3e2beadd 100644
--- a/client/components/cards/cardDetails.styl
+++ b/client/components/cards/cardDetails.styl
@@ -4,6 +4,12 @@
avatar-radius = 50%
+#cardURL_copy
+ // Have clipboard text not visible by moving it to far left
+ position: absolute
+ left: -2000px
+ top: 0px
+
.assignee
border-radius: 3px
display: block
@@ -88,17 +94,18 @@ avatar-radius = 50%
animation: flexGrowIn 0.1s
box-shadow: 0 0 7px 0 darken(white, 30%)
transition: flex-basis 0.1s
+ box-sizing: border-box
.mCustomScrollBox
padding-left: 0
.ps-scrollbar-y-rail
pointer-event: all
- position: absolute;
+ position: absolute
.card-details-canvas
width: 470px
- padding-left: 20px;
+ padding-left: 20px
.card-details-header
margin: 0 -20px 5px
@@ -108,6 +115,8 @@ avatar-radius = 50%
.close-card-details,
.card-details-menu,
+ .card-copy-button,
+ .card-copy-mobile-button,
.close-card-details-mobile-web,
.card-details-menu-mobile-web
float: right
@@ -122,6 +131,16 @@ avatar-radius = 50%
padding: 5px
margin-right: 40px
+ .card-copy-button
+ font-size: 17px
+ padding: 10px
+ margin-right: 10px
+
+ .card-copy-mobile-button
+ font-size: 17px
+ padding: 10px
+ margin-right: 10px
+
.card-details-menu
font-size: 17px
padding: 10px
@@ -223,7 +242,7 @@ input[type="submit"].attachment-add-link-submit
.card-details-canvas
width: 100%
- padding-left: 0px;
+ padding-left: 0px
.card-details-header
.close-card-details
@@ -312,3 +331,13 @@ card-details-color(background, color...)
.card-details-indigo
card-details-color(#4b0082, #ffffff) //White text for better visibility
+
+.voted
+ opacity: .7
+.vote-title
+ display: flex
+ justify-content: space-between
+.vote-result
+ display: flex
+.js-show-positive-votes
+ cursor: pointer
diff --git a/client/components/cards/checklists.jade b/client/components/cards/checklists.jade
index 279d3671..1b1e088a 100644
--- a/client/components/cards/checklists.jade
+++ b/client/components/cards/checklists.jade
@@ -1,5 +1,7 @@
template(name="checklists")
- h3 {{_ 'checklists'}}
+ h3
+ i.fa.fa-check
+ | {{_ 'checklists'}}
if toggleDeleteDialog.get
.board-overlay#card-details-overlay
+checklistDeleteDialog(checklist = checklistToDelete)
@@ -86,7 +88,8 @@ template(name="checklistItems")
template(name='checklistItemDetail')
.js-checklist-item.checklist-item
if canModifyCard
- .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
+ .check-box-container
+ .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
diff --git a/client/components/cards/checklists.js b/client/components/cards/checklists.js
index 57939eb8..29573d2b 100644
--- a/client/components/cards/checklists.js
+++ b/client/components/cards/checklists.js
@@ -1,4 +1,4 @@
-const { calculateIndexData, enableClickOnTouch } = Utils;
+const { calculateIndexData, capitalize } = Utils;
function initSorting(items) {
items.sortable({
@@ -36,9 +36,6 @@ function initSorting(items) {
checklistItem.move(checklistId, sortIndex.base);
},
});
-
- // ugly touch event hotfix
- enableClickOnTouch('.js-checklist-item:not(.placeholder)');
}
BlazeComponent.extendComponent({
@@ -54,11 +51,15 @@ BlazeComponent.extendComponent({
return Meteor.user() && Meteor.user().isBoardMember();
}
- // Disable sorting if the current user is not a board member
+ // Disable sorting if the current user is not a board member or is a miniscreen
self.autorun(() => {
const $itemsDom = $(self.itemsDom);
- if ($itemsDom.data('sortable')) {
- $(self.itemsDom).sortable('option', 'disabled', !userIsMember());
+ if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) {
+ $(self.itemsDom).sortable(
+ 'option',
+ 'disabled',
+ !userIsMember() || Utils.isMiniScreen(),
+ );
}
});
},
@@ -67,7 +68,8 @@ BlazeComponent.extendComponent({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
}).register('checklistDetail');
@@ -120,7 +122,8 @@ BlazeComponent.extendComponent({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
@@ -172,6 +175,16 @@ BlazeComponent.extendComponent({
}
},
+ focusChecklistItem(event) {
+ // If a new checklist is created, pre-fill the title and select it.
+ const checklist = this.currentData().checklist;
+ if (!checklist) {
+ const textarea = event.target;
+ textarea.value = capitalize(TAPi18n.__('r-checklist'));
+ textarea.select();
+ }
+ },
+
events() {
const events = {
'click .toggle-delete-checklist-dialog'(event) {
@@ -191,6 +204,7 @@ BlazeComponent.extendComponent({
'submit .js-edit-checklist-item': this.editChecklistItem,
'click .js-delete-checklist-item': this.deleteItem,
'click .confirm-checklist-delete': this.deleteChecklist,
+ 'focus .js-add-checklist-item': this.focusChecklistItem,
keydown: this.pressKey,
},
];
@@ -228,7 +242,8 @@ Template.checklistItemDetail.helpers({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
});
@@ -244,7 +259,7 @@ BlazeComponent.extendComponent({
events() {
return [
{
- 'click .js-checklist-item .check-box': this.toggleItem,
+ 'click .js-checklist-item .check-box-container': this.toggleItem,
},
];
},
diff --git a/client/components/cards/checklists.styl b/client/components/cards/checklists.styl
index 8ac37a15..0a6d688b 100644
--- a/client/components/cards/checklists.styl
+++ b/client/components/cards/checklists.styl
@@ -113,6 +113,9 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
&:hover
background-color: darken(white, 8%)
+ .check-box-container
+ padding-right: 1px;
+
.check-box
margin: 0.1em 0 0 0;
&.is-checked
@@ -121,7 +124,7 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
.item-title
flex: 1
- padding-left: 10px;
+ margin-left: 10px;
&.is-checked
color: #8c8c8c
font-style: italic
diff --git a/client/components/cards/labels.styl b/client/components/cards/labels.styl
index 9d7c7553..ee946656 100644
--- a/client/components/cards/labels.styl
+++ b/client/components/cards/labels.styl
@@ -158,6 +158,8 @@
.edit-labels-pop-over
margin-bottom: 8px
+ .card-label .viewer p
+ margin: 0
.edit-labels-pop-over .shortcut
display: inline-block
diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade
index 7dd220ee..b6ccd4d7 100644
--- a/client/components/cards/minicard.jade
+++ b/client/components/cards/minicard.jade
@@ -100,6 +100,10 @@ template(name="minicard")
if getDescription
.badge.badge-state-image-only(title=getDescription)
span.badge-icon.fa.fa-align-left
+ if getVoteQuestion
+ .badge.badge-state-image-only(title=getVoteQuestion)
+ span.badge-icon.fa.fa-thumbs-up
+ span.badge-icon.fa.fa-thumbs-down
if attachments.count
.badge
span.badge-icon.fa.fa-paperclip
diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js
index 1ea608f5..da36b87f 100644
--- a/client/components/cards/minicard.js
+++ b/client/components/cards/minicard.js
@@ -36,24 +36,20 @@ Template.minicard.helpers({
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
+ } else if (cookies.has('showDesktopDragHandles')) {
+ return true;
} else {
- if (cookies.has('showDesktopDragHandles')) {
- return true;
- } else {
- return false;
- }
+ return false;
}
},
hiddenMinicardLabelText() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).hiddenMinicardLabelText;
+ } else if (cookies.has('hiddenMinicardLabelText')) {
+ return true;
} else {
- if (cookies.has('hiddenMinicardLabelText')) {
- return true;
- } else {
- return false;
- }
+ return false;
}
},
});
diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl
index 8607e118..7d72a588 100644
--- a/client/components/cards/minicard.styl
+++ b/client/components/cards/minicard.styl
@@ -79,7 +79,7 @@
border-radius: top 2px
.minicard-labels
- float: right
+ float: none
display: flex
flex-wrap: wrap
diff --git a/client/components/cards/subtasks.jade b/client/components/cards/subtasks.jade
index 7e64e23f..df35bed3 100644
--- a/client/components/cards/subtasks.jade
+++ b/client/components/cards/subtasks.jade
@@ -1,5 +1,7 @@
template(name="subtasks")
- h3 {{_ 'subtasks'}}
+ h3
+ i.fa.fa-sitemap
+ | {{_ 'subtasks'}}
if toggleDeleteDialog.get
.board-overlay#card-details-overlay
+subtaskDeleteDialog(subtask = subtaskToDelete)
diff --git a/client/components/cards/subtasks.js b/client/components/cards/subtasks.js
index fab860bb..4cd15c11 100644
--- a/client/components/cards/subtasks.js
+++ b/client/components/cards/subtasks.js
@@ -3,7 +3,8 @@ BlazeComponent.extendComponent({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
}).register('subtaskDetail');
@@ -19,7 +20,22 @@ BlazeComponent.extendComponent({
const crtBoard = Boards.findOne(card.boardId);
const targetBoard = crtBoard.getDefaultSubtasksBoard();
const listId = targetBoard.getDefaultSubtasksListId();
- const swimlaneId = targetBoard.getDefaultSwimline()._id;
+
+ //Get the full swimlane data for the parent task.
+ const parentSwimlane = Swimlanes.findOne({
+ boardId: crtBoard._id,
+ _id: card.swimlaneId,
+ });
+ //find the swimlane of the same name in the target board.
+ const targetSwimlane = Swimlanes.findOne({
+ boardId: targetBoard._id,
+ title: parentSwimlane.title,
+ });
+ //If no swimlane with a matching title exists in the target board, fall back to the default swimlane.
+ const swimlaneId =
+ targetSwimlane === undefined
+ ? targetBoard.getDefaultSwimline()._id
+ : targetSwimlane._id;
if (title) {
const _id = Cards.insert({
@@ -55,7 +71,8 @@ BlazeComponent.extendComponent({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
@@ -154,7 +171,8 @@ Template.subtaskItemDetail.helpers({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
});