diff options
author | Nico <paetni1@gmail.com> | 2020-05-03 00:33:15 +0200 |
---|---|---|
committer | Nico <paetni1@gmail.com> | 2020-05-03 00:33:15 +0200 |
commit | 3cc0a93e0ea2399d239923e3a89d49d93a979684 (patch) | |
tree | ad1dbb6ce522229d18388bb1d45cb287d0314b07 | |
parent | 533bc045d06269dba2f42cdfe61817a1b3407974 (diff) | |
download | wekan-3cc0a93e0ea2399d239923e3a89d49d93a979684.tar.gz wekan-3cc0a93e0ea2399d239923e3a89d49d93a979684.tar.bz2 wekan-3cc0a93e0ea2399d239923e3a89d49d93a979684.zip |
Card vote options in new fork
-rw-r--r-- | client/components/boards/boardsList.js | 3 | ||||
-rw-r--r-- | client/components/cards/cardDate.js | 27 | ||||
-rw-r--r-- | client/components/cards/cardDetails.jade | 79 | ||||
-rw-r--r-- | client/components/cards/cardDetails.js | 102 | ||||
-rw-r--r-- | client/components/cards/cardDetails.styl | 5 | ||||
-rw-r--r-- | client/components/cards/minicard.jade | 2 | ||||
-rwxr-xr-x | client/components/main/editor.js | 2 | ||||
-rw-r--r-- | i18n/en.i18n.json | 12 | ||||
-rw-r--r-- | models/cards.js | 70 | ||||
-rw-r--r-- | sandstorm.js | 2 |
10 files changed, 239 insertions, 65 deletions
diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index 9208fdb2..b99c0c31 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -25,7 +25,6 @@ BlazeComponent.extendComponent({ }, onRendered() { - const self = this; function userIsAllowedToMove() { return Meteor.user(); } @@ -78,7 +77,7 @@ BlazeComponent.extendComponent({ }, boards() { - let query = { + const query = { archived: false, type: 'board', }; diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index c4b5c6d8..9b2268e9 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -386,3 +386,30 @@ CardEndDate.register('cardEndDate'); return this.date.get().format('l'); } }.register('minicardEndDate')); + +class VoteEndDate extends CardDate { + onCreated() { + super.onCreated(); + const self = this; + self.autorun(() => { + self.date.set(moment(self.data().getVoteEnd())); + }); + } + classes() { + const classes = 'end-date' + ' '; + return classes; + } + showDate() { + return this.date.get().format('l LT'); + } + showTitle() { + return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`; + } + + events() { + return super.events().concat({ + 'click .js-edit-date': Popup.open('editVoteEndDate'), + }); + } +} +VoteEndDate.register('voteEndDate'); diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index ae97e0e9..9f3b188b 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -202,9 +202,12 @@ template(name="cardDetails") if getVoteQuestion hr .vote-title - h3 - i.fa.fa-thumbs-up - card-details-item-title {{_ 'vote-question'}} + div.flex + h3 + i.fa.fa-thumbs-up + | {{_ 'vote-question'}} + if getVoteEnd + +voteEndDate .vote-result if votePublic a.card-label.card-label-green.js-show-positive-votes {{ voteCountPositive }} @@ -212,10 +215,13 @@ template(name="cardDetails") else .card-label.card-label-green {{ voteCountPositive }} .card-label.card-label-red {{ voteCountNegative }} + unless ($and currentBoard.isPublic voteAllowNonBoardMembers ) + .card-label.card-label-gray {{ voteCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }} +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'}} + if showVotingButtons + 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 @@ -333,16 +339,10 @@ 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-start-voting + i.fa.fa-thumbs-up + | {{_ 'card-edit-voting'}} li a.js-custom-fields i.fa.fa-list-alt @@ -465,14 +465,14 @@ template(name="cardAssigneesPopup") 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 + 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}})") @@ -564,20 +564,39 @@ template(name="setCardColorPopup") template(name="cardDeletePopup") p {{_ "card-delete-pop"}} unless archived - p {{_ "card-delete-suggest-archive"}} + p {{_ "card-delete-suggest-archive"}} + button.js-confirm.negate.full(type="submit") {{_ 'delete'}} + +template(name="deleteVotePopup") + p {{_ "vote-delete-pop"}} 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") + input.js-vote-field#vote(type="text" name="vote" value="{{getVoteQuestion}}" autofocus disabled="{{#if getVoteQuestion}}disabled{{/if}}") + .check-div + a.flex(class="{{#if getVoteQuestion}}is-disabled{{else}}js-toggle-vote-allow-non-members{{/if}}") + .materialCheckBox#vote-allow-non-members(name="vote-allow-non-members" class="{{#if voteAllowNonBoardMembers}}is-checked{{/if}}") + span {{_ 'allowNonBoardMembers'}} + .check-div + a.flex(class="{{#if getVoteQuestion}}is-disabled{{else}}js-toggle-vote-public{{/if}}") + .materialCheckBox#vote-public(name="vote-public" class="{{#if votePublic}}is-checked{{/if}}") + span {{_ 'vote-public'}} + .check-div.flex + i.fa.fa-hourglass-end + a.js-end-date + span + | {{_ 'card-end'}} + unless getVoteEnd + i.fa.fa-plus + if getVoteEnd + +voteEndDate - button.primary.confirm.js-submit {{_ 'save'}} - //- button.js-remove-color.negate.wide.right {{_ 'delete'}} + button.primary.js-submit {{_ 'save'}} + if getVoteQuestion + button.js-remove-vote.negate.wide.right {{_ 'delete'}} template(name="positiveVoteMembersPopup") ul.pop-over-list.js-card-member-list diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 271fbe2f..7dcadfe3 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -54,21 +54,6 @@ BlazeComponent.extendComponent({ } 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()); @@ -148,6 +133,15 @@ BlazeComponent.extendComponent({ return result; }, + showVotingButtons() { + const card = this.currentData(); + return ( + (currentUser.isBoardMember() || + (currentUser && card.voteAllowNonBoardMembers())) && + !card.expiredVote() + ); + }, + onRendered() { if (Meteor.settings.public.CARD_OPENED_WEBHOOK_ENABLED) { // Send Webhook but not create Activities records --- @@ -611,11 +605,6 @@ 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( @@ -1000,22 +989,93 @@ BlazeComponent.extendComponent({ events() { return [ { + 'click .js-end-date': Popup.open('editVoteEndDate'), '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); + const allowNonBoardMembers = $('#vote-allow-non-members').hasClass( + 'is-checked', + ); + const endString = this.currentCard.getVoteEnd(); + + this.currentCard.setVoteQuestion( + voteQuestion, + publicVote, + allowNonBoardMembers, + ); + if (endString) { + this.currentCard.setVoteEnd(endString); + } Popup.close(); }, + 'click .js-remove-vote': Popup.afterConfirm('deleteVote', () => { + event.preventDefault(); + this.currentCard.unsetVote(); + Popup.close(); + }), 'click a.js-toggle-vote-public'(event) { event.preventDefault(); $('#vote-public').toggleClass('is-checked'); }, + 'click a.js-toggle-vote-allow-non-members'(event) { + event.preventDefault(); + $('#vote-allow-non-members').toggleClass('is-checked'); + }, }, ]; }, }).register('cardStartVotingPopup'); +// editVoteEndDatePopup +(class extends DatePicker { + onCreated() { + super.onCreated(moment().format('YYYY-MM-DD HH:mm')); + this.data().getVoteEnd() && this.date.set(moment(this.data().getVoteEnd())); + } + events() { + return [ + { + 'submit .edit-date'(evt) { + evt.preventDefault(); + + // if no time was given, init with 12:00 + const time = + evt.target.time.value || + moment(new Date().setHours(12, 0, 0)).format('LT'); + + const dateString = `${evt.target.date.value} ${time}`; + const newDate = moment(dateString, 'L LT', true); + if (newDate.isValid()) { + // if active vote - store it + if (this.currentData().getVoteQuestion()) { + this._storeDate(newDate.toDate()); + Popup.close(); + } else { + this.currentData().vote = { end: newDate.toDate() }; // set vote end temp + Popup.back(); + } + } else { + this.error.set('invalid-date'); + evt.target.date.focus(); + } + }, + 'click .js-delete-date'(evt) { + evt.preventDefault(); + this._deleteDate(); + Popup.close(); + }, + }, + ]; + } + _storeDate(newDate) { + this.card.setVoteEnd(newDate); + } + _deleteDate() { + this.card.unsetVoteEnd(); + } +}.register('editVoteEndDatePopup')); + // 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 3e2beadd..cfdc450d 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -337,6 +337,11 @@ card-details-color(background, color...) .vote-title display: flex justify-content: space-between + + .js-edit-date + align-self: baseline + margin-left: 5px + .vote-result display: flex .js-show-positive-votes diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade index b6ccd4d7..79dd9127 100644 --- a/client/components/cards/minicard.jade +++ b/client/components/cards/minicard.jade @@ -103,7 +103,9 @@ template(name="minicard") if getVoteQuestion .badge.badge-state-image-only(title=getVoteQuestion) span.badge-icon.fa.fa-thumbs-up + span.badge-text {{ voteCountPositive }} span.badge-icon.fa.fa-thumbs-down + span.badge-text {{ voteCountNegative }} if attachments.count .badge span.badge-icon.fa.fa-paperclip diff --git a/client/components/main/editor.js b/client/components/main/editor.js index 081c6521..0c2e3186 100755 --- a/client/components/main/editor.js +++ b/client/components/main/editor.js @@ -330,7 +330,7 @@ Template.viewer.events({ // the corresponding text). Clicking a link shouldn't fire these actions, stop // we stop these event at the viewer component level. 'click a'(event, templateInstance) { - let prevent = true; + const prevent = true; const userId = event.currentTarget.dataset.userid; if (userId) { Popup.open('member').call({ userId }, event, templateInstance); diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 11e7e2dd..06593b20 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -152,8 +152,6 @@ "card-spent": "Spent Time", "card-edit-attachments": "Edit attachments", "card-edit-custom-fields": "Edit custom fields", - "card-start-voting": "Start voting", - "card-cancel-voting": "Delete voting and all votes", "card-edit-labels": "Edit labels", "card-edit-members": "Edit members", "card-labels-title": "Change the labels for the card.", @@ -166,11 +164,15 @@ "cardStartVotingPopup-title": "Start a vote", "positiveVoteMembersPopup-title": "Proponents", "negativeVoteMembersPopup-title": "Opponents", - "allowNonBoardMembers": "Allow anonymous vote on public board", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", "vote-question": "Voting question", "vote-public": "Show who voted what", "vote-for-it": "for it", "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -642,8 +644,6 @@ "r-when-a-member": "When a member is", "r-when-the-member": "When the member", "r-name": "name", - "r-is": "is", - "r-when-a-attach": "When an attachment", "r-when-a-checklist": "When a checklist is", "r-when-the-checklist": "When the checklist", "r-completed": "Completed", @@ -656,7 +656,6 @@ "r-top-of": "Top of", "r-bottom-of": "Bottom of", "r-its-list": "its list", - "r-list": "list", "r-archive": "Move to Archive", "r-unarchive": "Restore from Archive", "r-card": "card", @@ -712,7 +711,6 @@ "r-swimlane-name": "swimlane name", "r-board-note": "Note: leave a field empty to match every possible value. ", "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", - "r-added-to": "added to", "r-when-a-card-is-moved": "When a card is moved to another list", "r-set": "Set", "r-update": "Update", diff --git a/models/cards.js b/models/cards.js index 4197f7ab..b0783898 100644 --- a/models/cards.js +++ b/models/cards.js @@ -340,6 +340,10 @@ Cards.attachSchema( type: Boolean, defaultValue: false, }, + 'vote.allowNonBoardMembers': { + type: Boolean, + defaultValue: false, + }, }), ); @@ -347,8 +351,14 @@ Cards.allow({ insert(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - update(userId, doc) { - return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); + + update(userId, doc, fields) { + // Allow board members or logged in users if only vote get's changed + return ( + allowIsBoardMember(userId, Boards.findOne(doc.boardId)) || + (_.isEqual(fields, ['vote', 'modifiedAt', 'dateLastActivity']) && + !!userId) + ); }, remove(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); @@ -1048,6 +1058,29 @@ Cards.helpers({ } }, + getVoteEnd() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + if (card && card.vote) return card.vote.end; + else return null; + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId }); + if (board && board.vote) return board.vote.end; + else return null; + } else if (this.vote) { + return this.vote.end; + } else { + return null; + } + }, + expiredVote() { + let end = this.getVoteEnd(); + if (end) { + end = moment(end); + return end.isBefore(new Date()); + } + return false; + }, voteMemberPositive() { if (this.vote && this.vote.positive) return Users.find({ _id: { $in: this.vote.positive } }); @@ -1153,6 +1186,26 @@ Cards.helpers({ isTemplateCard() { return this.type === 'template-card'; }, + + votePublic() { + if (this.vote) return this.vote.public; + return null; + }, + voteAllowNonBoardMembers() { + if (this.vote) return this.vote.allowNonBoardMembers; + return null; + }, + voteCountNegative() { + if (this.vote && this.vote.negative) return this.vote.negative.length; + return null; + }, + voteCountPositive() { + if (this.vote && this.vote.positive) return this.vote.positive.length; + return null; + }, + voteCount() { + return this.voteCountPositive() + this.voteCountNegative(); + }, }); Cards.mutations({ @@ -1476,12 +1529,13 @@ Cards.mutations({ }, }; }, - setVoteQuestion(question, publicVote) { + setVoteQuestion(question, publicVote, allowNonBoardMembers) { return { $set: { vote: { question, public: publicVote, + allowNonBoardMembers, positive: [], negative: [], }, @@ -1495,6 +1549,16 @@ Cards.mutations({ }, }; }, + setVoteEnd(end) { + return { + $set: { 'vote.end': end }, + }; + }, + unsetVoteEnd() { + return { + $unset: { 'vote.end': '' }, + }; + }, setVote(userId, forIt) { switch (forIt) { case true: diff --git a/sandstorm.js b/sandstorm.js index 8615e419..de386d14 100644 --- a/sandstorm.js +++ b/sandstorm.js @@ -22,7 +22,7 @@ const sandstormBoard = { if (isSandstorm && Meteor.isServer) { const fs = require('fs'); - const Capnp = Npm.require(`capnp`); + const Capnp = Npm.require('capnp'); const Package = Capnp.importSystem('sandstorm/package.capnp'); const Powerbox = Capnp.importSystem('sandstorm/powerbox.capnp'); const Identity = Capnp.importSystem('sandstorm/identity.capnp'); |