summaryrefslogtreecommitdiffstats
path: root/client/components/cards
diff options
context:
space:
mode:
Diffstat (limited to 'client/components/cards')
-rw-r--r--client/components/cards/attachments.jade2
-rw-r--r--client/components/cards/cardDetails.jade73
-rw-r--r--client/components/cards/cardDetails.js109
-rw-r--r--client/components/cards/cardDetails.styl17
-rw-r--r--client/components/cards/checklists.jade3
-rw-r--r--client/components/cards/checklists.js31
-rw-r--r--client/components/cards/checklists.styl5
-rw-r--r--client/components/cards/labels.styl2
-rw-r--r--client/components/cards/minicard.jade8
-rw-r--r--client/components/cards/minicard.styl2
-rw-r--r--client/components/cards/subtasks.js17
11 files changed, 224 insertions, 45 deletions
diff --git a/client/components/cards/attachments.jade b/client/components/cards/attachments.jade
index 57e46e39..eda6d118 100644
--- a/client/components/cards/attachments.jade
+++ b/client/components/cards/attachments.jade
@@ -62,5 +62,5 @@ template(name="attachmentsGalery")
unless currentUser.isWorker
//li.attachment-item.add-attachment
a.js-add-attachment
- i.fa.fa-paperclip
+ i.fa.fa-plus
| {{_ 'add-attachment' }}
diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade
index 615ae1d5..ae97e0e9 100644
--- a/client/components/cards/cardDetails.jade
+++ b/client/components/cards/cardDetails.jade
@@ -32,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}}
@@ -199,10 +199,29 @@ template(name="cardDetails")
+viewer
= getAssignedBy
+ if getVoteQuestion
+ hr
+ .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'}}
+
//- 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'}}
@@ -229,6 +248,7 @@ template(name="cardDetails")
a.js-close-inlined-form {{_ 'discard'}}
else if getDescription
if currentBoard.allowsDescriptionTitle
+ hr
h3.card-details-item-title {{_ 'description'}}
if currentBoard.allowsDescriptionText
+viewer
@@ -237,15 +257,16 @@ template(name="cardDetails")
.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'}}
+ hr
+ h3
+ i.fa.fa-paperclip
+ | {{_ 'attachments'}}
.card-checklist-attachmentGalery.card-attachmentGalery
+attachmentsGalery
@@ -312,6 +333,16 @@ template(name="cardDetailsActionsPopup")
//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
@@ -535,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 5fdc5579..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());
@@ -200,9 +231,6 @@ BlazeComponent.extendComponent({
},
});
- // ugly touch event hotfix
- enableClickOnTouch('.card-checklist-items .js-checklist');
-
const $subtasksDom = this.$('.card-subtasks-items');
$subtasksDom.sortable({
@@ -238,26 +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());
- }
- if ($subtasksDom.data('sortable')) {
- $subtasksDom.sortable('option', 'disabled', !userIsMember());
- }
- if ($checklistsDom.data('sortable')) {
- $checklistsDom.sortable('option', 'disabled', Utils.isMiniScreen());
+ 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', Utils.isMiniScreen());
+ if ($subtasksDom.data('uiSortable') || $subtasksDom.data('sortable')) {
+ $subtasksDom.sortable('option', 'disabled', disabled);
}
});
},
@@ -347,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'),
@@ -356,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.
@@ -379,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);
+ },
},
];
},
@@ -560,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'),
@@ -570,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(
@@ -672,7 +718,7 @@ BlazeComponent.extendComponent({
_id: { $ne: Meteor.user().getTemplatesBoardId() },
},
{
- sort: ['title'],
+ sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -848,7 +894,7 @@ BlazeComponent.extendComponent({
},
},
{
- sort: ['title'],
+ sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -945,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 80fa87c0..3e2beadd 100644
--- a/client/components/cards/cardDetails.styl
+++ b/client/components/cards/cardDetails.styl
@@ -94,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
@@ -241,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
@@ -330,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 391769e9..1b1e088a 100644
--- a/client/components/cards/checklists.jade
+++ b/client/components/cards/checklists.jade
@@ -88,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 c88fdd82..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,14 +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('sortable')) {
- $(self.itemsDom).sortable('option', 'disabled', Utils.isMiniScreen());
+ if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) {
+ $(self.itemsDom).sortable(
+ 'option',
+ 'disabled',
+ !userIsMember() || Utils.isMiniScreen(),
+ );
}
});
},
@@ -177,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) {
@@ -196,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,
},
];
@@ -250,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 6a073424..e8efc6ac 100644
--- a/client/components/cards/minicard.jade
+++ b/client/components/cards/minicard.jade
@@ -4,8 +4,8 @@ template(name="minicard")
class="{{#if isLinkedBoard}}linked-board{{/if}}"
class="minicard-{{colorClass}}")
if isMiniScreen
- //.handle
- // .fa.fa-arrows
+ .handle
+ .fa.fa-arrows
unless isMiniScreen
if showDesktopDragHandles
.handle
@@ -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.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.js b/client/components/cards/subtasks.js
index 34348fe1..4cd15c11 100644
--- a/client/components/cards/subtasks.js
+++ b/client/components/cards/subtasks.js
@@ -20,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({