diff options
author | Romulus Tsai 蔡仲明 <urakagi@gmail.com> | 2020-05-08 10:13:11 +0800 |
---|---|---|
committer | Romulus Tsai 蔡仲明 <urakagi@gmail.com> | 2020-05-08 10:13:11 +0800 |
commit | c3458855bdb52c976ee6689ad5a0d4e92e96f2e3 (patch) | |
tree | d9dbbcc3087b5bfc520710b5f5624a3f4e2b78e6 /client/components/cards | |
parent | 444848876759173ad80203129250d2f0311f30fc (diff) | |
parent | cfcc73724fcd394150d1b815d0a7a4c466e216b5 (diff) | |
download | wekan-c3458855bdb52c976ee6689ad5a0d4e92e96f2e3.tar.gz wekan-c3458855bdb52c976ee6689ad5a0d4e92e96f2e3.tar.bz2 wekan-c3458855bdb52c976ee6689ad5a0d4e92e96f2e3.zip |
Merge branch 'master' into lib-change
Diffstat (limited to 'client/components/cards')
-rw-r--r-- | client/components/cards/attachments.jade | 2 | ||||
-rw-r--r-- | client/components/cards/cardDetails.jade | 73 | ||||
-rw-r--r-- | client/components/cards/cardDetails.js | 109 | ||||
-rw-r--r-- | client/components/cards/cardDetails.styl | 17 | ||||
-rw-r--r-- | client/components/cards/checklists.jade | 3 | ||||
-rw-r--r-- | client/components/cards/checklists.js | 31 | ||||
-rw-r--r-- | client/components/cards/checklists.styl | 5 | ||||
-rw-r--r-- | client/components/cards/labels.styl | 2 | ||||
-rw-r--r-- | client/components/cards/minicard.jade | 8 | ||||
-rw-r--r-- | client/components/cards/minicard.styl | 2 | ||||
-rw-r--r-- | client/components/cards/subtasks.js | 17 |
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({ |